<commit_before>import ENV from './environment';
import * as Utils from './utils';
import { getDataParams, querySelector, querySelectorAll, getAttribute, setAttribute, addEventListener, hasClass, addClass, removeClass } from './dom';
import Mojito from './main';
import { applyObserver, addObserver, callObserver } from './observer';
import { applyComputed } from './computed';

/**
    registers a controller in mojito, creates an instance
    and does all the magic for setting up all the controller stuff
*/
export function registerController(name, ControllerClass) {

    // make sure registerController has exactly two arguments
    if(arguments.length < 2) {
        throw '[Type Exeption] missing parameters';
    }

    // check name to make sure it is an array
    if(!Utils.isString(name) ) {
        throw '[Type Exeption] name has to be a string';
    }

    // check ControllerClass to make sure it is a function or object
    if(!Utils.isFunction(ControllerClass) && !Utils.isObject(ControllerClass) ) {
        throw '[Type Exeption] No ControllerClass found for '+name+' (maybe you forgot to export '+name+' as default)';
    }

    // check for controller instances array
    if(!Utils.isArray(Mojito._controllerInstances)) {
        throw '[Type Exeption] No _controllerInstances found';
    }

    // grab elements from DOM where this controller has been attached
    let elements = querySelectorAll('[data-'+ ENV.HTMLDATA().CONTROLLER_DEF +'="'+name+'"]');

    // loop through elements and create controller instances
    for(let i=0, max=elements.length; i<max; i++) {

        let element = elements[i];
        let params = [];

        // check if controller is already registered (has an id)
        if(getAttribute(element, 'data-' + ENV.HTMLDATA().CONTROLLER_ID)) {
            continue;
        }

        // check if there are any params for controller init
        if(getAttribute(element, 'data-' + ENV.HTMLDATA().PARAMS)) {
            params = getDataParams(element);
        }

        // check if controller reference is set and apply it to instance
        let ref = getAttribute(element, 'data-' + ENV.HTMLDATA().CONTROLLER_REF);
        console.log(ENV.HTMLDATA().CONTROLLER_REF);

        // merge params with config
        params = params.concat([{
            element: element,
            id: Utils.generateRandomString(16),
            ref: ref,
            className: name
        }]);

        // create new controller instance from class
        // instead of new ControllerClass() using the following code to apply
        // the params as an arguments array
        let controller = new (Function.prototype.bind.apply(ControllerClass, [null].concat(params)));

    }
}

export function applyController(controller) {

     // check name to make sure it is an array
    if(typeof controller === 'undefined' ) {
        throw '[Type Exeption] controller has to be a object';
    }

    if(getAttribute(controller._$, 'data-'+ ENV.HTMLDATA().CONTROLLER_ID)) {
        return controller;
    }

    // apply id to dome node
    setAttribute(controller._$, 'data-'+ ENV.HTMLDATA().CONTROLLER_ID, controller.get('_id', true));

    // add controller instance to instances array for later access
    Mojito._controllerInstances.push(controller);

    // add all the computed properties on this controller
    applyComputed(controller);

    // add all the observers on this controller
    applyObserver(controller);

    // add dom logic to controller
    applyDomToController(controller);

    return controller;
}

export function applyDomToController(controller, rootElement) {

    // apply actions
    applyActionsToController(controller, rootElement);

    // apply class bindings
    applyClassBindingsToController(controller, rootElement);

    // apply input bindings
    applyInputBindingsToController(controller, rootElement);

    // apply content bindings
    applyContentBindingsToController(controller, rootElement);

    return controller;
}

export function applyActionsToController(controller, rootElement) {

    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let actionElements = Array.prototype.slice.call(querySelectorAll(rootElement, '[data-' + ENV.HTMLDATA().ACTION + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().ACTION)) {
         actionElements.push(rootElement);
    }

    let i = actionElements.length;

    while(i--) {
        let element = actionElements[i];
        let action = getAttribute(element, 'data-'+ ENV.HTMLDATA().ACTION);
        let id =  getAttribute(element, 'data-'+ ENV.HTMLDATA().ACTION_ID);
        let eventType =  getAttribute(element, 'data-'+ ENV.HTMLDATA().EVENT);

        if(id) {
            continue;
        }

        if(Utils.isString(action)) {
            let actionParts = action.split('.');

            if(actionParts.length === 2
                && actionParts[0] === controller.get('_className', true)
                && actionParts[1] in controller) {

                    if(!eventType || ENV.EVENTTYPES.split(' ').indexOf(eventType) === -1) {
                        eventType = ENV.DEFAULT_EVENTTYPE;
                    }

                    setAttribute(element, 'data-'+ ENV.HTMLDATA().ACTION_ID, Utils.generateRandomString(16));
                    addEventListener(element, eventType, function(event) {
                        event.preventDefault();
                        let params = getDataParams(element);
                        controller[actionParts[1]].apply(controller, params.concat([event]));
                    }, controller);
            }
        }
    }
}

export function applyClassBindingsToController(controller, rootElement) {
    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let bindingElements = Array.prototype.slice.call(querySelectorAll(rootElement,
        '[data-' + ENV.HTMLDATA().CLASSBINDING + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().CLASSBINDING)) {
         bindingElements.push(rootElement);
    }

    let i = bindingElements.length;

    while(i--) {
        let element = bindingElements[i];
        let bindings = getAttribute(element, 'data-'+ENV.HTMLDATA().CLASSBINDING).split(' ');
        let id =  getAttribute(element, 'data-'+ ENV.HTMLDATA().CLASSBINDING_ID);
        let j = bindings.length;

        if(id) {
            continue;
        }

        while(j--) {
            let binding = bindings[j];

            if(!Utils.isString(binding)) {
                continue;
            }

            binding = binding.split('.');

            if(binding.length !== 2 || binding[0] !== controller.get('_className', true)) {
                continue;
            }

            binding = binding[1].split(':');

            if(!binding.length || !binding[0].length) {
                continue;
            }

            let trigger = binding[0];
            let class1 = (binding.length > 1 && binding[1].length) ? binding[1] : null;
            let class2 = (binding.length > 2 && binding[2].length) ? binding[2] : null;

            const observerId = addObserver(controller, trigger, function(trigger, element, class1, class2) {
                if(this.get(trigger)) {
                    if(!!class1) {
                        addClass(element, class1);
                    } else {
                        addClass(element, trigger);
                    }
                    !!class2 && removeClass(element, class2);
                } else {
                    if(!!class1) {
                        removeClass(element, class1);
                    } else {
                        removeClass(element, trigger);
                    }
                    !!class2 && addClass(element, class2);
                }
            }, [trigger, element, class1, class2]);

            setAttribute(element, 'data-'+ ENV.HTMLDATA().CLASSBINDING_ID, observerId);

            callObserver(controller, trigger);
        }
    }
}

export function applyInputBindingsToController(controller, rootElement) {

    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let bindingElements = Array.prototype.slice.call(querySelectorAll(rootElement,
        '[data-' + ENV.HTMLDATA().INPUTBINDING + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().INPUTBINDING)) {
         bindingElements.push(rootElement);
    }

    let i = bindingElements.length;

    while(i--) {
        let element = bindingElements[i];
        let binding = getAttribute(element, 'data-'+ ENV.HTMLDATA().INPUTBINDING);
        let id =  getAttribute(element, 'data-'+ ENV.HTMLDATA().INPUTBINDING_ID);

        if(id) {
            continue;
        }

        if(!Utils.isString(binding)) {
            continue;
        }

        binding = binding.split('.');

        if(binding.length < 2 || binding[0] !== controller.get('_className', true)) {
            continue;
        }

        setAttribute(element, 'data-'+ ENV.HTMLDATA().INPUTBINDING_ID, Utils.generateRandomString(16));

        let param = binding.slice(1).join('.');
        let events = '';

        switch (element.tagName.toLowerCase()) {
            case 'textarea':
            case 'input':
                events = 'input change paste';
                break;
            case 'select':
                events = 'change';
                break;
        }
        (function(element, events, controller, param) {
            addEventListener(element, events, function(event) {
                if(element.type === 'checkbox') {
                    value = element.checked;
                } else if(element.type !== 'radio' || element.checked) {
                    value = element.value;
                }

                if(value !== controller.get(param)) {
                    controller.set(param, value);
                }
            });
        })(element, events, controller, param);

        const observerId = addObserver(controller, param, function(param, element) {
            switch (element.type) {
                case 'checkbox':
                    (element.checked !== !!controller.get(param)) && (element.checked = !!controller.get(param));
                    break;
                case 'radio':
                    element.checked = (controller.get(param) === element.value);
                    break;
                default:
                    if(controller.get(param) !== element.value) {
                        element.value = controller.get(param);
                    }
                break;
            }
        }, [param, element]);

        setAttribute(element, 'data-'+ENV.HTMLDATA().INPUTBINDING_ID, observerId);

        let value = null;
        if(element.type === 'checkbox') {
            value = element.checked;
        } else if(element.type !== 'radio' || element.checked) {
            value = element.value;
        }

        if(!!value) {
            controller.set(param, value);
        } else if(controller.get(param)){
            switch (element.type) {
                case 'checkbox':
                    (element.checked !== !!controller.get(param)) && (element.checked = !!controller.get(param));
                    break;
                case 'radio':
                    element.checked = (controller.get(param) === element.value);
                    break;
                default:
                    if(controller.get(param) !== element.value) {
                        element.value = controller.get(param);
                    }
                break;
            }
        }
    }

}

export function applyContentBindingsToController(controller, rootElement) {
    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let bindingElements = Array.prototype.slice.call(querySelectorAll(rootElement,
        '[data-' + ENV.HTMLDATA().CONTENTBINDING + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().CONTENTBINDING)) {
         bindingElements.push(rootElement);
    }

    let i = bindingElements.length;

    while(i--) {
        let element = bindingElements[i];
        let binding = getAttribute(element, 'data-'+ENV.HTMLDATA().CONTENTBINDING);
        let id =  getAttribute(element, 'data-'+ENV.HTMLDATA().CONTENTBINDING_ID);

        if(id) {
            continue;
        }

        if(!Utils.isString(binding)) {
            continue;
        }

        binding = binding.split('.');

        if(binding.length < 2 || binding[0] !== controller.get('_className', true)) {
            continue;
        }

        setAttribute(element, 'data-'+ ENV.HTMLDATA().CONTENTBINDING_ID, Utils.generateRandomString(16));

        let keyName = binding.slice(1).join('.');

        const observerId = addObserver(controller, keyName, function(keyName, element) {
            element.innerHTML = controller.get(keyName) ? controller.get(keyName) : '';
            setTimeout(function() {

                let elements = querySelectorAll(element, '[data-' + ENV.HTMLDATA().ACTION + ']');

                console.log(elements);

                applyDomToController(controller, element);
            }, 0)
        }, [keyName, element]);

        setAttribute(element, 'data-'+ ENV.HTMLDATA().CONTENTBINDING_ID, observerId);

        callObserver(controller, keyName);
    }
}

export function register(type, name, Class) {

    if(arguments.length < 3) {
        throw '[Type Exeption] missing parameters';
    }

    if(!Utils.isString(type) || !Utils.isString(name) ) {
        throw '[Type Exeption] type and name have to be strings';
    }

    if(type === 'controller') {
        return registerController(name, Class);
    }

    return null;

}

export function getControllersByParam(param, value) {

    // check for controller instances array
    if(!Utils.isArray(Mojito._controllerInstances)) {
        throw '[Type Exeption] No _controllerInstances found';
    }

    // check id to make sure it is a string
    if(!Utils.isString(value)) {
        console.error('[Type Error] '+ param +' has to be a string');
        return null;
    }

    let i = Mojito._controllerInstances.length;
    let result = [];
    while(i--) {
        let controller = Mojito._controllerInstances[i];
        if(controller.get(param) === value) {
            result.push(controller);
        }
    }
    return result;

}

export function getControllerById(id) {

    const controllers = getControllersByParam('_id', id);
    return controllers.length ? controllers[0] : null;

}

export function getControllerByRef(ref) {

    const controllers = getControllersByParam('_ref', ref);
    return controllers.length ? controllers[0] : null;

}

export function getControllersByClassName(name) {

    return getControllersByParam('_className', name);

}
<commit_msg>removed log<commit_after>import ENV from './environment';
import * as Utils from './utils';
import { getDataParams, querySelector, querySelectorAll, getAttribute, setAttribute, addEventListener, hasClass, addClass, removeClass } from './dom';
import Mojito from './main';
import { applyObserver, addObserver, callObserver } from './observer';
import { applyComputed } from './computed';

/**
    registers a controller in mojito, creates an instance
    and does all the magic for setting up all the controller stuff
*/
export function registerController(name, ControllerClass) {

    // make sure registerController has exactly two arguments
    if(arguments.length < 2) {
        throw '[Type Exeption] missing parameters';
    }

    // check name to make sure it is an array
    if(!Utils.isString(name) ) {
        throw '[Type Exeption] name has to be a string';
    }

    // check ControllerClass to make sure it is a function or object
    if(!Utils.isFunction(ControllerClass) && !Utils.isObject(ControllerClass) ) {
        throw '[Type Exeption] No ControllerClass found for '+name+' (maybe you forgot to export '+name+' as default)';
    }

    // check for controller instances array
    if(!Utils.isArray(Mojito._controllerInstances)) {
        throw '[Type Exeption] No _controllerInstances found';
    }

    // grab elements from DOM where this controller has been attached
    let elements = querySelectorAll('[data-'+ ENV.HTMLDATA().CONTROLLER_DEF +'="'+name+'"]');

    // loop through elements and create controller instances
    for(let i=0, max=elements.length; i<max; i++) {

        let element = elements[i];
        let params = [];

        // check if controller is already registered (has an id)
        if(getAttribute(element, 'data-' + ENV.HTMLDATA().CONTROLLER_ID)) {
            continue;
        }

        // check if there are any params for controller init
        if(getAttribute(element, 'data-' + ENV.HTMLDATA().PARAMS)) {
            params = getDataParams(element);
        }

        // check if controller reference is set and apply it to instance
        let ref = getAttribute(element, 'data-' + ENV.HTMLDATA().CONTROLLER_REF);

        // merge params with config
        params = params.concat([{
            element: element,
            id: Utils.generateRandomString(16),
            ref: ref,
            className: name
        }]);

        // create new controller instance from class
        // instead of new ControllerClass() using the following code to apply
        // the params as an arguments array
        let controller = new (Function.prototype.bind.apply(ControllerClass, [null].concat(params)));

    }
}

export function applyController(controller) {

     // check name to make sure it is an array
    if(typeof controller === 'undefined' ) {
        throw '[Type Exeption] controller has to be a object';
    }

    if(getAttribute(controller._$, 'data-'+ ENV.HTMLDATA().CONTROLLER_ID)) {
        return controller;
    }

    // apply id to dome node
    setAttribute(controller._$, 'data-'+ ENV.HTMLDATA().CONTROLLER_ID, controller.get('_id', true));

    // add controller instance to instances array for later access
    Mojito._controllerInstances.push(controller);

    // add all the computed properties on this controller
    applyComputed(controller);

    // add all the observers on this controller
    applyObserver(controller);

    // add dom logic to controller
    applyDomToController(controller);

    return controller;
}

export function applyDomToController(controller, rootElement) {

    // apply actions
    applyActionsToController(controller, rootElement);

    // apply class bindings
    applyClassBindingsToController(controller, rootElement);

    // apply input bindings
    applyInputBindingsToController(controller, rootElement);

    // apply content bindings
    applyContentBindingsToController(controller, rootElement);

    return controller;
}

export function applyActionsToController(controller, rootElement) {

    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let actionElements = Array.prototype.slice.call(querySelectorAll(rootElement, '[data-' + ENV.HTMLDATA().ACTION + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().ACTION)) {
         actionElements.push(rootElement);
    }

    let i = actionElements.length;

    while(i--) {
        let element = actionElements[i];
        let action = getAttribute(element, 'data-'+ ENV.HTMLDATA().ACTION);
        let id =  getAttribute(element, 'data-'+ ENV.HTMLDATA().ACTION_ID);
        let eventType =  getAttribute(element, 'data-'+ ENV.HTMLDATA().EVENT);

        if(id) {
            continue;
        }

        if(Utils.isString(action)) {
            let actionParts = action.split('.');

            if(actionParts.length === 2
                && actionParts[0] === controller.get('_className', true)
                && actionParts[1] in controller) {

                    if(!eventType || ENV.EVENTTYPES.split(' ').indexOf(eventType) === -1) {
                        eventType = ENV.DEFAULT_EVENTTYPE;
                    }

                    setAttribute(element, 'data-'+ ENV.HTMLDATA().ACTION_ID, Utils.generateRandomString(16));
                    addEventListener(element, eventType, function(event) {
                        event.preventDefault();
                        let params = getDataParams(element);
                        controller[actionParts[1]].apply(controller, params.concat([event]));
                    }, controller);
            }
        }
    }
}

export function applyClassBindingsToController(controller, rootElement) {
    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let bindingElements = Array.prototype.slice.call(querySelectorAll(rootElement,
        '[data-' + ENV.HTMLDATA().CLASSBINDING + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().CLASSBINDING)) {
         bindingElements.push(rootElement);
    }

    let i = bindingElements.length;

    while(i--) {
        let element = bindingElements[i];
        let bindings = getAttribute(element, 'data-'+ENV.HTMLDATA().CLASSBINDING).split(' ');
        let id =  getAttribute(element, 'data-'+ ENV.HTMLDATA().CLASSBINDING_ID);
        let j = bindings.length;

        if(id) {
            continue;
        }

        while(j--) {
            let binding = bindings[j];

            if(!Utils.isString(binding)) {
                continue;
            }

            binding = binding.split('.');

            if(binding.length !== 2 || binding[0] !== controller.get('_className', true)) {
                continue;
            }

            binding = binding[1].split(':');

            if(!binding.length || !binding[0].length) {
                continue;
            }

            let trigger = binding[0];
            let class1 = (binding.length > 1 && binding[1].length) ? binding[1] : null;
            let class2 = (binding.length > 2 && binding[2].length) ? binding[2] : null;

            const observerId = addObserver(controller, trigger, function(trigger, element, class1, class2) {
                if(this.get(trigger)) {
                    if(!!class1) {
                        addClass(element, class1);
                    } else {
                        addClass(element, trigger);
                    }
                    !!class2 && removeClass(element, class2);
                } else {
                    if(!!class1) {
                        removeClass(element, class1);
                    } else {
                        removeClass(element, trigger);
                    }
                    !!class2 && addClass(element, class2);
                }
            }, [trigger, element, class1, class2]);

            setAttribute(element, 'data-'+ ENV.HTMLDATA().CLASSBINDING_ID, observerId);

            callObserver(controller, trigger);
        }
    }
}

export function applyInputBindingsToController(controller, rootElement) {

    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let bindingElements = Array.prototype.slice.call(querySelectorAll(rootElement,
        '[data-' + ENV.HTMLDATA().INPUTBINDING + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().INPUTBINDING)) {
         bindingElements.push(rootElement);
    }

    let i = bindingElements.length;

    while(i--) {
        let element = bindingElements[i];
        let binding = getAttribute(element, 'data-'+ ENV.HTMLDATA().INPUTBINDING);
        let id =  getAttribute(element, 'data-'+ ENV.HTMLDATA().INPUTBINDING_ID);

        if(id) {
            continue;
        }

        if(!Utils.isString(binding)) {
            continue;
        }

        binding = binding.split('.');

        if(binding.length < 2 || binding[0] !== controller.get('_className', true)) {
            continue;
        }

        setAttribute(element, 'data-'+ ENV.HTMLDATA().INPUTBINDING_ID, Utils.generateRandomString(16));

        let param = binding.slice(1).join('.');
        let events = '';

        switch (element.tagName.toLowerCase()) {
            case 'textarea':
            case 'input':
                events = 'input change paste';
                break;
            case 'select':
                events = 'change';
                break;
        }
        (function(element, events, controller, param) {
            addEventListener(element, events, function(event) {
                if(element.type === 'checkbox') {
                    value = element.checked;
                } else if(element.type !== 'radio' || element.checked) {
                    value = element.value;
                }

                if(value !== controller.get(param)) {
                    controller.set(param, value);
                }
            });
        })(element, events, controller, param);

        const observerId = addObserver(controller, param, function(param, element) {
            switch (element.type) {
                case 'checkbox':
                    (element.checked !== !!controller.get(param)) && (element.checked = !!controller.get(param));
                    break;
                case 'radio':
                    element.checked = (controller.get(param) === element.value);
                    break;
                default:
                    if(controller.get(param) !== element.value) {
                        element.value = controller.get(param);
                    }
                break;
            }
        }, [param, element]);

        setAttribute(element, 'data-'+ENV.HTMLDATA().INPUTBINDING_ID, observerId);

        let value = null;
        if(element.type === 'checkbox') {
            value = element.checked;
        } else if(element.type !== 'radio' || element.checked) {
            value = element.value;
        }

        if(!!value) {
            controller.set(param, value);
        } else if(controller.get(param)){
            switch (element.type) {
                case 'checkbox':
                    (element.checked !== !!controller.get(param)) && (element.checked = !!controller.get(param));
                    break;
                case 'radio':
                    element.checked = (controller.get(param) === element.value);
                    break;
                default:
                    if(controller.get(param) !== element.value) {
                        element.value = controller.get(param);
                    }
                break;
            }
        }
    }

}

export function applyContentBindingsToController(controller, rootElement) {
    // make sure applyDomToController has exactly one arguments
    if(arguments.length < 1) {
        throw '[Type Exeption] missing parameters';
    }

    // check controller to make sure it is an object
    if(!Utils.isObject(controller) ) {
        throw '[Type Exeption] controller has to be an object';
    }

    if(typeof rootElement !== 'object') {
        rootElement = controller.get('_$', true);
    }

    let bindingElements = Array.prototype.slice.call(querySelectorAll(rootElement,
        '[data-' + ENV.HTMLDATA().CONTENTBINDING + ']'));

    if(getAttribute(rootElement, 'data-' + ENV.HTMLDATA().CONTENTBINDING)) {
         bindingElements.push(rootElement);
    }

    let i = bindingElements.length;

    while(i--) {
        let element = bindingElements[i];
        let binding = getAttribute(element, 'data-'+ENV.HTMLDATA().CONTENTBINDING);
        let id =  getAttribute(element, 'data-'+ENV.HTMLDATA().CONTENTBINDING_ID);

        if(id) {
            continue;
        }

        if(!Utils.isString(binding)) {
            continue;
        }

        binding = binding.split('.');

        if(binding.length < 2 || binding[0] !== controller.get('_className', true)) {
            continue;
        }

        setAttribute(element, 'data-'+ ENV.HTMLDATA().CONTENTBINDING_ID, Utils.generateRandomString(16));

        let keyName = binding.slice(1).join('.');

        const observerId = addObserver(controller, keyName, function(keyName, element) {
            element.innerHTML = controller.get(keyName) ? controller.get(keyName) : '';
            setTimeout(function() {

                let elements = querySelectorAll(element, '[data-' + ENV.HTMLDATA().ACTION + ']');

                applyDomToController(controller, element);
            }, 0)
        }, [keyName, element]);

        setAttribute(element, 'data-'+ ENV.HTMLDATA().CONTENTBINDING_ID, observerId);

        callObserver(controller, keyName);
    }
}

export function register(type, name, Class) {

    if(arguments.length < 3) {
        throw '[Type Exeption] missing parameters';
    }

    if(!Utils.isString(type) || !Utils.isString(name) ) {
        throw '[Type Exeption] type and name have to be strings';
    }

    if(type === 'controller') {
        return registerController(name, Class);
    }

    return null;

}

export function getControllersByParam(param, value) {

    // check for controller instances array
    if(!Utils.isArray(Mojito._controllerInstances)) {
        throw '[Type Exeption] No _controllerInstances found';
    }

    // check id to make sure it is a string
    if(!Utils.isString(value)) {
        console.error('[Type Error] '+ param +' has to be a string');
        return null;
    }

    let i = Mojito._controllerInstances.length;
    let result = [];
    while(i--) {
        let controller = Mojito._controllerInstances[i];
        if(controller.get(param) === value) {
            result.push(controller);
        }
    }
    return result;

}

export function getControllerById(id) {

    const controllers = getControllersByParam('_id', id);
    return controllers.length ? controllers[0] : null;

}

export function getControllerByRef(ref) {

    const controllers = getControllersByParam('_ref', ref);
    return controllers.length ? controllers[0] : null;

}

export function getControllersByClassName(name) {

    return getControllersByParam('_className', name);

}
<|endoftext|>


<commit_before>'use strict';

var usage = require('usage'),
    psTree = require('ps-tree'),
    step = require('step'),
    exec = require('child_process').exec,
    _ = require('underscore'),
    lxc = require('lxc'),
    sprintf = require('sprintf').sprintf;

var core = {};

var parseLxcPs = function(stdout) {
  var processes = stdout.split(/\n/);
  var params = [];

  for (var i=0; i<processes.length; i++) {
    params = processes[i].split(/\s+/);      // CONTAINER PID TTY TIME CMD

    if (params[4] == "init") {
      return params[1];
    }
  };
};

core.cpuUsage = function (program) {
  var containerName = program.name;
  var cpuUsageValue = 0.0;

  step(
    function() {
      var lxcPs = "lxc-ps --name=" + program.name;
      var child = exec(lxcPs, this);
    },
    function(err, stdout, stderr) {
      var containerInitPid = parseLxcPs(stdout);

      console.log(sprintf("lxc-cpu-usage - Target LXC:%s, Target PID:%s", program.name, containerInitPid));
      psTree(containerInitPid, this);
    },
    function(err, children) {

      var displayUsage = _.after(children.length, function(){
        console.log("------------------");
        console.log(sprintf("Processes: %d, CPU usage:%5.2f", children.length, cpuUsageValue));
      });

      if (program.verbose) {
        console.log("------------------");
        console.log("PID\t%CPU");
      }

      _.each(children, function(target) {
        var pid = target.PID;

        usage.lookup(pid, function(err, result) {
          if (!err) {
            var oneUsageCpu = parseFloat(result.cpu);

            if (program.verbose) {
              console.log(sprintf("%s\t%5.2f", pid, oneUsageCpu));
            }

            cpuUsageValue += oneUsageCpu;
            displayUsage();
          }
        });
      });
    }
  );
};

module.exports = core;

<commit_msg>対象LXCが存在しない場合の表示追記<commit_after>'use strict';

var usage = require('usage'),
    psTree = require('ps-tree'),
    step = require('step'),
    exec = require('child_process').exec,
    _ = require('underscore'),
    lxc = require('lxc'),
    sprintf = require('sprintf').sprintf;

var core = {};

var parseLxcPs = function(stdout) {
  var processes = stdout.split(/\n/);
  var params = [];

  for (var i=0; i<processes.length; i++) {
    params = processes[i].split(/\s+/);      // CONTAINER PID TTY TIME CMD

    if (params[4] == "init") {
      return params[1];
    }
  };
};

core.cpuUsage = function (program) {
  var containerName = program.name;
  var cpuUsageValue = 0.0;

  step(
    function() {
      var lxcPs = "lxc-ps --name=" + program.name;
      var child = exec(lxcPs, this);
    },
    function(err, stdout, stderr) {
      var containerInitPid = parseLxcPs(stdout);

      if (typeof containerInitPid === 'undefined') {
        console.log(sprintf("lxc-cpu-usage - Target LXC:%s not found.", program.name));
      } else {
        console.log(sprintf("lxc-cpu-usage - Target LXC:%s, Target PID:%s", program.name, containerInitPid));
        psTree(containerInitPid, this);
      }
    },
    function(err, children) {
      var displayUsage = _.after(children.length, function(){
        console.log("------------------");
        console.log(sprintf("Processes: %d, CPU usage:%5.2f", children.length, cpuUsageValue));
      });

      if (program.verbose) {
        console.log("------------------");
        console.log("PID\t%CPU");
      }

      _.each(children, function(target) {
        var pid = target.PID;

        usage.lookup(pid, function(err, result) {
          if (!err) {
            var oneUsageCpu = parseFloat(result.cpu);

            if (program.verbose) {
              console.log(sprintf("%s\t%5.2f", pid, oneUsageCpu));
            }

            cpuUsageValue += oneUsageCpu;
            displayUsage();
          }
        });
      });
    }
  );
};

module.exports = core;

<|endoftext|>


<commit_before>var absURLRegEx = /^[^\/]+:\/\//;

function readMemberExpression(p, value) {
  var pParts = p.split('.');
  while (pParts.length)
    value = value[pParts.shift()];
  return value;
}

var baseURLCache = {};
function getBaseURLObj() {
  if (baseURLCache[this.baseURL])
    return baseURLCache[this.baseURL];

  // normalize baseURL if not already
  if (this.baseURL[this.baseURL.length - 1] != '/')
    this.baseURL += '/';

  var baseURL = new URL(this.baseURL, baseURI);

  this.baseURL = baseURL.href;

  return (baseURLCache[this.baseURL] = baseURL);
}

var baseURIObj = new URL(baseURI);

(function() {

hookConstructor(function(constructor) {
  return function() {
    constructor.call(this);

    // support baseURL
    this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1);

    // support the empty module, as a concept
    this.set('@empty', this.newModule({}));
  };
});

/*
  Normalization

  If a name is relative, we apply URL normalization to the page
  If a name is an absolute URL, we leave it as-is

  Plain names (neither of the above) run through the map and package
  normalization phases (applying before and after this one).

  The paths normalization phase applies last (paths extension), which
  defines the `normalizeSync` function and normalizes everything into
  a URL.

  The final normalization
 */
hook('normalize', function() {
  return function(name, parentName) {
    // relative URL-normalization
    if (name[0] == '.' || name[0] == '/')
      return new URL(name, parentName || baseURIObj).href;
    return name;
  };
});

/*
  __useDefault

  When a module object looks like:
  newModule(
    __useDefault: true,
    default: 'some-module'
  })

  Then importing that module provides the 'some-module'
  result directly instead of the full module.

  Useful for eg module.exports = function() {}
*/
hook('import', function(systemImport) {
  return function(name, parentName, parentAddress) {
    return systemImport.call(this, name, parentName, parentAddress).then(function(module) {
      return module.__useDefault ? module['default'] : module;
    });
  };
});

/*
 Extend config merging one deep only

  loader.config({
    some: 'random',
    config: 'here',
    deep: {
      config: { too: 'too' }
    }
  });

  <=>

  loader.some = 'random';
  loader.config = 'here'
  loader.deep = loader.deep || {};
  loader.deep.config = { too: 'too' };

  Normalizes meta and package configs allowing for:

  System.config({
    meta: {
      './index.js': {}
    }
  });

  To become

  System.meta['https://thissite.com/index.js'] = {};

  For easy normalization canonicalization with latest URL support.

*/
SystemJSLoader.prototype.config = function(cfg) {

  // always configure baseURL first
  if (cfg.baseURL) {
    var hasConfig = false;
    function checkHasConfig(obj) {
      for (var p in obj)
        return true;
    }
    if (checkHasConfig(this.packages) || checkHasConfig(this.meta) || checkHasConfig(this.depCache) || checkHasConfig(this.bundles))
      throw new TypeError('baseURL should only be configured once and must be configured first.');

    this.baseURL = cfg.baseURL;

    // sanitize baseURL
    getBaseURLObj.call(this);
  }

  if (cfg.paths) {
    for (var p in cfg.paths)
      this.paths[p] = cfg.paths[p];
  }

  if (cfg.map) {
    for (var p in cfg.map) {
      var v = cfg.map[p];

      // object map backwards-compat into packages configuration
      if (typeof v !== 'string') {
        var normalized = this.normalizeSync(p);

        // if doing default js extensions, undo to get package name
        if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
          normalized = normalized.substr(0, normalized.length - 3);

        // if a package main, revert it
        var pkgMatch = '';
        for (var pkg in this.packages) {
          if (normalized.substr(0, pkg.length) == pkg
              && (!normalized[pkg.length] || normalized[pkg.length] == '/')
              && pkgMatch.split('/').length < pkg.split('/').length)
            pkgMatch = pkg;
        }
        if (pkgMatch && this.packages[pkgMatch].main)
          normalized = normalized.substr(0, normalized.length - this.packages[pkgMatch].main.length - 1);

        var pkg = this.packages[normalized] = this.packages[normalized] || {};
        pkg.map = v;
      }
      else {
        this.map[p] = v;
      }
    }
  }

  if (cfg.packages) {
    for (var p in cfg.packages) {
      var prop = this.normalizeSync(p);

      // if doing default js extensions, undo to get package name
      if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
        prop = prop.substr(0, prop.length - 3);

      this.packages[prop]= this.packages[prop] || {};
      for (var q in cfg.packages[p])
        this.packages[prop][q] = cfg.packages[p][q];
    }
  }

  if (cfg.bundles) {
    for (var p in cfg.bundles) {
      var bundle = [];
      for (var i = 0; i < cfg.bundles[p].length; i++)
        bundle.push(this.normalizeSync(cfg.bundles[p][i]));
      this.bundles[p] = bundle;
    }
  }

  for (var c in cfg) {
    var v = cfg[c];
    var normalizeProp = false, normalizeValArray = false;

    if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles' || c == 'paths')
      continue;

    if (typeof v != 'object' || v instanceof Array) {
      this[c] = v;
    }
    else {
      this[c] = this[c] || {};

      if (c == 'meta' || c == 'depCache')
        normalizeProp = true;

      for (var p in v) {
        if (c == 'meta' && p[0] == '*')
          this[c][p] = v[p];
        else if (normalizeProp)
          this[c][this.normalizeSync(p)] = v[p];
        else
          this[c][p] = v[p];
      }
    }
  }
};

})();<commit_msg>ensure defaultJSExtensions config applies first<commit_after>var absURLRegEx = /^[^\/]+:\/\//;

function readMemberExpression(p, value) {
  var pParts = p.split('.');
  while (pParts.length)
    value = value[pParts.shift()];
  return value;
}

var baseURLCache = {};
function getBaseURLObj() {
  if (baseURLCache[this.baseURL])
    return baseURLCache[this.baseURL];

  // normalize baseURL if not already
  if (this.baseURL[this.baseURL.length - 1] != '/')
    this.baseURL += '/';

  var baseURL = new URL(this.baseURL, baseURI);

  this.baseURL = baseURL.href;

  return (baseURLCache[this.baseURL] = baseURL);
}

var baseURIObj = new URL(baseURI);

(function() {

hookConstructor(function(constructor) {
  return function() {
    constructor.call(this);

    // support baseURL
    this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1);

    // support the empty module, as a concept
    this.set('@empty', this.newModule({}));
  };
});

/*
  Normalization

  If a name is relative, we apply URL normalization to the page
  If a name is an absolute URL, we leave it as-is

  Plain names (neither of the above) run through the map and package
  normalization phases (applying before and after this one).

  The paths normalization phase applies last (paths extension), which
  defines the `normalizeSync` function and normalizes everything into
  a URL.

  The final normalization
 */
hook('normalize', function() {
  return function(name, parentName) {
    // relative URL-normalization
    if (name[0] == '.' || name[0] == '/')
      return new URL(name, parentName || baseURIObj).href;
    return name;
  };
});

/*
  __useDefault

  When a module object looks like:
  newModule(
    __useDefault: true,
    default: 'some-module'
  })

  Then importing that module provides the 'some-module'
  result directly instead of the full module.

  Useful for eg module.exports = function() {}
*/
hook('import', function(systemImport) {
  return function(name, parentName, parentAddress) {
    return systemImport.call(this, name, parentName, parentAddress).then(function(module) {
      return module.__useDefault ? module['default'] : module;
    });
  };
});

/*
 Extend config merging one deep only

  loader.config({
    some: 'random',
    config: 'here',
    deep: {
      config: { too: 'too' }
    }
  });

  <=>

  loader.some = 'random';
  loader.config = 'here'
  loader.deep = loader.deep || {};
  loader.deep.config = { too: 'too' };

  Normalizes meta and package configs allowing for:

  System.config({
    meta: {
      './index.js': {}
    }
  });

  To become

  System.meta['https://thissite.com/index.js'] = {};

  For easy normalization canonicalization with latest URL support.

*/
SystemJSLoader.prototype.config = function(cfg) {

  // always configure baseURL first
  if (cfg.baseURL) {
    var hasConfig = false;
    function checkHasConfig(obj) {
      for (var p in obj)
        return true;
    }
    if (checkHasConfig(this.packages) || checkHasConfig(this.meta) || checkHasConfig(this.depCache) || checkHasConfig(this.bundles))
      throw new TypeError('baseURL should only be configured once and must be configured first.');

    this.baseURL = cfg.baseURL;

    // sanitize baseURL
    getBaseURLObj.call(this);
  }

  if (cfg.defaultJSExtensions)
    this.defaultJSExtensions = cfg.defaultJSExtensions;

  if (cfg.paths) {
    for (var p in cfg.paths)
      this.paths[p] = cfg.paths[p];
  }

  if (cfg.map) {
    for (var p in cfg.map) {
      var v = cfg.map[p];

      // object map backwards-compat into packages configuration
      if (typeof v !== 'string') {
        var normalized = this.normalizeSync(p);

        // if doing default js extensions, undo to get package name
        if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
          normalized = normalized.substr(0, normalized.length - 3);

        // if a package main, revert it
        var pkgMatch = '';
        for (var pkg in this.packages) {
          if (normalized.substr(0, pkg.length) == pkg
              && (!normalized[pkg.length] || normalized[pkg.length] == '/')
              && pkgMatch.split('/').length < pkg.split('/').length)
            pkgMatch = pkg;
        }
        if (pkgMatch && this.packages[pkgMatch].main)
          normalized = normalized.substr(0, normalized.length - this.packages[pkgMatch].main.length - 1);

        var pkg = this.packages[normalized] = this.packages[normalized] || {};
        pkg.map = v;
      }
      else {
        this.map[p] = v;
      }
    }
  }

  if (cfg.packages) {
    for (var p in cfg.packages) {
      var prop = this.normalizeSync(p);

      // if doing default js extensions, undo to get package name
      if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
        prop = prop.substr(0, prop.length - 3);

      this.packages[prop]= this.packages[prop] || {};
      for (var q in cfg.packages[p])
        this.packages[prop][q] = cfg.packages[p][q];
    }
  }

  if (cfg.bundles) {
    for (var p in cfg.bundles) {
      var bundle = [];
      for (var i = 0; i < cfg.bundles[p].length; i++)
        bundle.push(this.normalizeSync(cfg.bundles[p][i]));
      this.bundles[p] = bundle;
    }
  }

  for (var c in cfg) {
    var v = cfg[c];
    var normalizeProp = false, normalizeValArray = false;

    if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles' || c == 'paths')
      continue;

    if (typeof v != 'object' || v instanceof Array) {
      this[c] = v;
    }
    else {
      this[c] = this[c] || {};

      if (c == 'meta' || c == 'depCache')
        normalizeProp = true;

      for (var p in v) {
        if (c == 'meta' && p[0] == '*')
          this[c][p] = v[p];
        else if (normalizeProp)
          this[c][this.normalizeSync(p)] = v[p];
        else
          this[c][p] = v[p];
      }
    }
  }
};

})();<|endoftext|>


<commit_before>/*
* @Author: dmyang
* @Date:   2015-11-10 10:21:22
* @Last Modified by:   dmyang
* @Last Modified time: 2016-06-01 10:56:50
* @Description: Hybrid core code
*/

'use strict'

const WIN = window
const UA = navigator.userAgent
const SLICE = Array.prototype.slice
const IS_ARR = Array.isArray
const EC = WIN.encodeURIComponent

const OS_NAME = 'HybridApp' // 推荐替换成公司标识，如淘宝“TaobaoCN”
const API_ROOT = 'HybridJS'
const SCHEMA = 'hybrid' // 私有协议，建议替换成公司私有协议，如腾讯“tencent”
const WEB_CB_SN_PREFIX = '__t_'
const REQ_SN_KEY = 'req_sn'
const RES_SN_KEY = 'res_sn'

const CALLBACKS = {}
const HANDLERS = {}

const Hybrid = WIN[API_ROOT] = {}
// ${OS_NAME}/o2o/1
let m = UA.match(new RegExp(OS_NAME + '\\/([^\\/]+)\\/(\\d+)'))

freeze('version', '1.0.0')
freeze('desc', `JavaScript layer for ${OS_NAME} hybrid framework.`)
freeze('deviceName', () => {
    // Mozilla/5.0 (Linux Android 5.1 MX5 Build/LMY471) AppleWebKit/600.1.3
    // return matchArray(UA, ['m1', 'm2', 'm note', 'm note2', 'm metal', 'MX3', 'MX4', 'MX4 Pro', 'MX5', 'PRO5', 'Unknown'])
    let m = UA.match(/\s*([^]+)\s+Build/)
    return m ? m[1] : 'Unknown'
})
freeze('os', matchArray(UA, [OS_NAME, 'Unknown']))
freeze('isInApp', Hybrid.os == OS_NAME)
freeze('appName', m ? m[1] : '')
freeze('appVersion', m ? m[2] : 0)

Hybrid.debug = 0
// 所有web提供给Native的接口都放到Hybrid.web对象上，避免冲突
Hybrid.web = {}

freeze('wrapAPI', () => wrapAPI)
freeze('assign', () => assignAPI)
freeze('invokeApp', () => invokeAppApi)
freeze('invokeWeb', () => invokeWebApi)
freeze('notifyWeb', () => appNotifyWeb)
freeze('on', () => on)
freeze('off', () => off)
// freeze('emit', () => emit)
freeze('util', () => {return { serialize, resolveUrl, parseUrl }})

/**
 * 把调用Native接口（即Hybrid.invokeApp）封装成js命名空间的方式
 * @param  {String}   api     api name
 * @param  {Function} wrapper 处理函数
 * @usage:
 * wrapAPI('device.getStatus', function(key, callback) {
 *     Hybrid.invokeApp('status.get', {key, callback}) // => hybrid://status/get?key=xx&req_sn=xxxxxx
 * })
 * @call:
 * Hybrid.device.getStatus('xx', function(result) {})
 */
function wrapAPI(api, wrapper) {
    if(/^web\./.test(api)) throw new Error('此处不要使用`web`作为根命名空间')
    inject(api, wrapper)
}

/**
 * 开放web api供app调用
 * @param  {String}   api      api name
 * @param  {Function} handler  处理函数
 * @usage:
 * assignAPI('web.dom.getNodeText', function(options) {return $(options.id).text()})
 */
function assignAPI(api, handler) {
    // if(/^app\./.test(api)) throw new Error('此处不要使用`app`作为根命名空间')
    inject('web', api, handler)
}

function inject(ns, api, handler) {
    if(arguments.length < 3) {
        handler = arguments[1]
        api = arguments[0]
        ns = ''
    }

    let names = api.split(/\./)
    let fnName = names.pop()

    if(names[0] === ns) names.shift()

    let root = ns ? (Hybrid[ns] || (Hybrid[ns] = {})) : Hybrid

    root = createNamespace(root, names)

    if(fnName && isFn(handler)) root[fnName] = handler
}

/**
 * invoke native
 * @param  {String} api     some api assigned
 * @param  {Object}         params calls options
 * @return {Boolean}        is invoke success or not
 * @usage:
 * invokeAppApi('com.companyName.hybridSDK.handler.Network/checkAvailable', {callback: function(result) {}})
 */
function invokeAppApi(api, params) {
    if(!api) return false

    let url = `${SCHEMA}://${api}`

    if(!params) return openURL(url)

    url += '?'

    params = isFn(params) ? {callback: params} : params

    if(typeof params !== 'object') throw TypeError('params show be an Object or Function or Undefined')

    // web callback Native
    if(RES_SN_KEY in params) {
        url += `${RES_SN_KEY}=${EC(params[RES_SN_KEY])}&`
        delete params[RES_SN_KEY]
    }

    let callback = params.callback || null

    if(isFn(callback)) {
        let sn = callbackSign(
            api,
            callback,
            params.context || Hybrid,
            params.nextTick !== undefined ? params.nextTick : true
        )
        // params[REQ_SN_KEY] = sn
        delete params.callback
        delete params.context
        delete params.nextTick

        url += `${REQ_SN_KEY}=${EC(sn)}&`
    }

    url += 'value=' + EC(JSON.stringify(params))

    openURL(url)
}

/**
 * native 调用web方法以及app回调web
 * 挂在window下面也可以，不过为了方便维护，建议都挂到Hybrid.web对象上
 * @param  {String} api
 * @param  {String} str
 * @usage:
 * invokeWebApi('com.companyName.hybridSDK.handler.Network/checkAvailable', "{value: true, res_sn: 111}")
 */
function invokeWebApi(api, str) {
    if(typeof api !== 'string' || (str && typeof str !== 'string')) throw Error('参数不合法，必须是字符串')

    if(str) {
        let params = parseStr(str)
        let resSN = params ? params[RES_SN_KEY] : null
        // 回调
        if(resSN) {
            let callback = findCallback(api, resSN)

            delete params[RES_SN_KEY]

            if(isFn(callback)) callback(params.value)
        } else {
            // 调用web api
            appCallWebApi(api, str, params)
        }
    } else {
        appCallWebApi(api)
    }
}

function appCallWebApi(api, str, params) {
    let names = api.split(/\./)

    // window.api() or web.api()
    if(/window|web/.test(names[0])) names.shift()

    if(!names.length) return

    let fnName = names.pop()
    // 查找web的处理函数，其实就是对象和方法的简单映射：
    // dom.getNodeText => Hybrid.web.dom.getNodeText() 方法需要预先“挂载”到Hybrid.web上
    // 优先查找挂在Hybrid.web对象上的方法
    let handler = createNamespace(Hybrid.web, names)[fnName] || createNamespace(WIN, names)[fnName]

    if(!isFn(handler)) throw Error(`调用的方法 ${fnName} 不存在`)

    if(!params) {
        // 支持invokeWebApi('api', 'arg')这种调用方式
        handler(str)
    } else {
        // 没有回调的情况
        if(!(REQ_SN_KEY in params) || typeof String(params[REQ_SN_KEY]) !== 'string') {
            handler(params.value)
            return
        }

        let sn = params[REQ_SN_KEY]

        delete params[REQ_SN_KEY]

        let val = handler(params.value)
        let response = {}

        if(typeof val == 'object') {
            if(val instanceof HTMLElement)
                throw Error('HTMLElement instance is not allowed to return to native.')
        }

        response[RES_SN_KEY] = sn
        response['value'] = val

        // native must assign api `*.web.callback` first
        // invokeAppApi('web.callback', response)
        invokeAppApi(api.replace(/\//g, '.'), response)
    }
}

/**
 * Event
 * @param  {String} event
 * @param  {String} str optional
 * @useage:
 * appNotifyWeb('com.companyName.hybridSDK.event.eventName', JSON.stringify({value: 'xxx'}))
*/
function appNotifyWeb(event, str) {
    if(typeof event !== 'string' || (str && typeof str !== 'string')) throw Error('参数不合法，必须是字符串')

    if(str) {
        let params = parseStr(str)
        emit(event, params.value)
    } else {
        emit(event)
    }
}

function parseStr(str) {
    str = decodeURIComponent(str)

    let params

    try {
        params = JSON.parse(str)
    } catch(e) {
        if(/{/.test(str)) throw e
        params = null
    }

    return params
}

/**
 * on('mback', function(data) {})
 */
function on(event, handler) {
    if(!event) return

    let fns = HANDLERS[event]

    if(!isFn(handler)) return false

    if(!handler.name) console.warn(`绑定事件的handler请尽量不用匿名函数！`)

    if(!fns) {
        fns = handler
        // 只通知客户端监听一次
        invokeAppApi(`${event}/listen`)
    } else {
        if(!IS_ARR(fns)) fns = [fns]
        if(IS_ARR(fns) && ~fns.indexOf(handler)) return
        fns.push(handler)
    }

    HANDLERS[event] = fns
}

function emit(event, data) {
    if(!event) {
        if(Hybrid.debug) console.warn('Receive empty event')
        return
    }

    let fns = HANDLERS[event]

    if(IS_ARR(fns)) fns.forEach((fn) => {fn(data)})
    else if(isFn(fns)) fns(data)
}

function off(event, handler) {
    if(!event) return

    invokeAppApi(`${event}/removeListen`)

    if(!(event in HANDLERS)) return

    if(isFn(handler)) {
        let fns = HANDLERS[event]

        if(IS_ARR(fns)) {
            HANDLERS[event] = fns.filter((fn) => {
                return fn != handler
            })

            HANDLERS[event].length !== fns.length
        } else {
            fns == handler ? delete HANDLERS[event] : false
        }
    } else {
        delete HANDLERS[event]
    }
}

function createNamespace(root, names) {
    let i = 0
    let a

    while(a = names[i++]) {
        if(a in root && 'object' !== type(root[a])) throw Error(`${a} 命名空间冲突`)
        if(!(a in root)) root[a] = {}
        root = root[a]
    }

    return root
}

function callbackSign(api, callback, context, nextTick) {
    if(isFn(callback)) {
        // api = api.replace(/\./g, '/')
        let sn = WEB_CB_SN_PREFIX + Date.now()
        let map = CALLBACKS[api] || (CALLBACKS[api] = {})

        // 注意，箭头函数没有arguments！！
        map[sn] = function(value) {
            let args = SLICE.call(arguments)

            if(!nextTick) {
                callback.apply(context, args)
            } else {
                setTimeout(() => {
                    callback.apply(context, args)
                }, 0)
            }

            delete map[sn]
        }

        return sn
    }

    return ''
}

function findCallback(api, sn) {
    // api = api.replace(/\./g, '/')

    return (CALLBACKS[api] || {})[sn]
}

function openURL(url) {
    // for debug in browsers
    if(!Hybrid.isInApp || Hybrid.debug) console.info('Request url: %s\nOrigin url: %s', url, decodeURIComponent(url))

    if(!Hybrid.isInApp) return

    let iframe = document.createElement('iframe')

    // Android必须先赋值再append
    document.body.appendChild(iframe)

    iframe.style.cssText = 'display:none;width:0px;height:0px'
    iframe.src = url
    // iframe.onload = function() {}

    setTimeout(function() {
        iframe.parentNode.removeChild(iframe)
    }, 0)

    return true
}

function freeze(k, v, o = Hybrid) {
    if('__defineGetter__' in o) o.__defineGetter__(k, () => {return isFn(v) ? v() : v})
    else o[k] = isFn(v) ? v() : v
}

function serialize(obj) {
    let r = []

    for(let k in obj) {
        let v = obj[k]

        if(typeof v == 'object') v = JSON.stringify(v)

        r.push(k + '=' + v)
    }

    return r.join('&')
}

function matchArray(str, arr) {
    for(let i=0; i<arr.length; i++) {
        if(new RegExp(arr[i]).test(str)) return arr[i].replace(/\s+/, '')
    }

    return 'Other'
}

function resolveUrl(url) {
    if(!url) return url

    url = url.trim()

    if(!/^http(''|s)?:\/\//.test(url)) {
        let parsed = parseUrl(url)

        url = url.replace(/^(?:\.)?\//,'')
        url = parsed.protocol + '//' + parsed.host + '/' + url
    }

    return url
}

function parseUrl(url) {
    let a = document.createElement('a')

    a.href = (url || 'x.html')

    return {
        protocol: a.protocol.replace(':', ''),
        host: a.host,
        port: a.port,
        pathname: a.pathname,
        search: a.search,
        hash: a.hash
    }
}

function type(o) {
    return Object.prototype.toString.apply(o).slice(8, -1).toLowerCase()
}

function isFn(fn) {
    return type(fn) === 'function'
}

function noop() {
    let args = SLICE.call(arguments)
    let callback = args.length && args[args.length - 1]

    return isFn(callback) ? callback(null) : null
}

module.exports = Hybrid
<commit_msg>fix iframe<commit_after>/*
* @Author: dmyang
* @Date:   2015-11-10 10:21:22
* @Last Modified by:   dmyang
* @Last Modified time: 2016-07-28 20:59:27
* @Description: Hybrid core code
*/

'use strict'

const WIN = window
const UA = navigator.userAgent
const SLICE = Array.prototype.slice
const IS_ARR = Array.isArray
const EC = WIN.encodeURIComponent
// 判断是否在iframe里边加载此js
const IS_IN_IFRAME = WIN.top !== WIN.self

const OS_NAME = 'HybridApp' // 推荐替换成公司标识，如淘宝“TaobaoCN”
const API_ROOT = 'HybridJS'
const SCHEMA = 'hybrid' // 私有协议，建议替换成公司私有协议，如腾讯“tencent”
const WEB_CB_SN_PREFIX = '__t_'
const REQ_SN_KEY = 'req_sn'
const RES_SN_KEY = 'res_sn'

const CALLBACKS = {}
const HANDLERS = {}

const Hybrid = WIN[API_ROOT] = {}
// ${OS_NAME}/o2o/1
let m = UA.match(new RegExp(OS_NAME + '\\/([^\\/]+)\\/(\\d+)'))

freeze('version', '1.0.0')
freeze('desc', `JavaScript layer for ${OS_NAME} hybrid framework.`)
freeze('deviceName', () => {
    // Mozilla/5.0 (Linux Android 5.1 MX5 Build/LMY471) AppleWebKit/600.1.3
    // return matchArray(UA, ['m1', 'm2', 'm note', 'm note2', 'm metal', 'MX3', 'MX4', 'MX4 Pro', 'MX5', 'PRO5', 'Unknown'])
    let m = UA.match(/\s*([^]+)\s+Build/)
    return m ? m[1] : 'Unknown'
})
freeze('os', matchArray(UA, [OS_NAME, 'Unknown']))
freeze('isInApp', Hybrid.os == OS_NAME)
freeze('appName', m ? m[1] : '')
freeze('appVersion', m ? m[2] : 0)

Hybrid.debug = 0
// 所有web提供给Native的接口都放到Hybrid.web对象上，避免冲突
Hybrid.web = {}

freeze('wrapAPI', () => wrapAPI)
freeze('assign', () => assignAPI)
freeze('invokeApp', () => invokeAppApi)
freeze('invokeWeb', () => invokeWebApi)
freeze('notifyWeb', () => appNotifyWeb)
freeze('on', () => on)
freeze('off', () => off)
// freeze('emit', () => emit)
freeze('util', () => {return { serialize, resolveUrl, parseUrl }})

// 这里只处理了iframe一层嵌套的情况
if(IS_IN_IFRAME) {
    console.info(`${API_ROOT} running in iframe.`)
    const parent = WIN.parent
    const iframes = parent.document.getElementsByTagName('iframe')
    const selfHref = WIN.self.location.href
    let selfWin
    for(let i=0; i<iframes.length; i++)
        if(iframes[i].contentWindow.location.href === selfHref) selfWin = iframes[i].contentWindow
    // WIN.parent[API_ROOT] = Hybrid
    WIN.parent[API_ROOT] = {
        invokeWeb: selfWin[API_ROOT].invokeWeb
    }
}

/**
 * 把调用Native接口（即Hybrid.invokeApp）封装成js命名空间的方式
 * @param  {String}   api     api name
 * @param  {Function} wrapper 处理函数
 * @usage:
 * wrapAPI('device.getStatus', function(key, callback) {
 *     Hybrid.invokeApp('status.get', {key, callback}) // => hybrid://status/get?key=xx&req_sn=xxxxxx
 * })
 * @call:
 * Hybrid.device.getStatus('xx', function(result) {})
 */
function wrapAPI(api, wrapper) {
    if(/^web\./.test(api)) throw new Error('此处不要使用`web`作为根命名空间')
    inject(api, wrapper)
}

/**
 * 开放web api供app调用
 * @param  {String}   api      api name
 * @param  {Function} handler  处理函数
 * @usage:
 * assignAPI('web.dom.getNodeText', function(options) {return $(options.id).text()})
 */
function assignAPI(api, handler) {
    // if(/^app\./.test(api)) throw new Error('此处不要使用`app`作为根命名空间')
    inject('web', api, handler)
}

function inject(ns, api, handler) {
    if(arguments.length < 3) {
        handler = arguments[1]
        api = arguments[0]
        ns = ''
    }

    let names = api.split(/\./)
    let fnName = names.pop()

    if(names[0] === ns) names.shift()

    let root = ns ? (Hybrid[ns] || (Hybrid[ns] = {})) : Hybrid

    root = createNamespace(root, names)

    if(fnName && isFn(handler)) root[fnName] = handler
}

/**
 * invoke native
 * @param  {String} api     some api assigned
 * @param  {Object}         params calls options
 * @return {Boolean}        is invoke success or not
 * @usage:
 * invokeAppApi('com.companyName.hybridSDK.handler.Network/checkAvailable', {callback: function(result) {}})
 */
function invokeAppApi(api, params) {
    if(!api) return false

    let url = `${SCHEMA}://${api}`

    if(!params) return openURL(url)

    url += '?'

    params = isFn(params) ? {callback: params} : params

    if(typeof params !== 'object') throw TypeError('params show be an Object or Function or Undefined')

    // web callback Native
    if(RES_SN_KEY in params) {
        url += `${RES_SN_KEY}=${EC(params[RES_SN_KEY])}&`
        delete params[RES_SN_KEY]
    }

    let callback = params.callback || null

    if(isFn(callback)) {
        let sn = callbackSign(
            api,
            callback,
            params.context || Hybrid,
            params.nextTick !== undefined ? params.nextTick : true
        )
        // params[REQ_SN_KEY] = sn
        delete params.callback
        delete params.context
        delete params.nextTick

        url += `${REQ_SN_KEY}=${EC(sn)}&`
    }

    url += 'value=' + EC(JSON.stringify(params))

    openURL(url)
}

/**
 * native 调用web方法以及app回调web
 * 挂在window下面也可以，不过为了方便维护，建议都挂到Hybrid.web对象上
 * @param  {String} api
 * @param  {String} str
 * @usage:
 * invokeWebApi('com.companyName.hybridSDK.handler.Network/checkAvailable', "{value: true, res_sn: 111}")
 */
function invokeWebApi(api, str) {
    if(typeof api !== 'string' || (str && typeof str !== 'string')) throw Error('参数不合法，必须是字符串')

    if(str) {
        let params = parseStr(str)
        let resSN = params ? params[RES_SN_KEY] : null
        // 回调
        if(resSN) {
            let callback = findCallback(api, resSN)

            delete params[RES_SN_KEY]

            if(isFn(callback)) callback(params.value)
        } else {
            // 调用web api
            appCallWebApi(api, str, params)
        }
    } else {
        appCallWebApi(api)
    }
}

function appCallWebApi(api, str, params) {
    let names = api.split(/\./)

    // window.api() or web.api()
    if(/window|web/.test(names[0])) names.shift()

    if(!names.length) return

    let fnName = names.pop()
    // 查找web的处理函数，其实就是对象和方法的简单映射：
    // dom.getNodeText => Hybrid.web.dom.getNodeText() 方法需要预先“挂载”到Hybrid.web上
    // 优先查找挂在Hybrid.web对象上的方法
    let handler = createNamespace(Hybrid.web, names)[fnName] || createNamespace(WIN, names)[fnName]

    if(!isFn(handler)) throw Error(`调用的方法 ${fnName} 不存在`)

    if(!params) {
        // 支持invokeWebApi('api', 'arg')这种调用方式
        handler(str)
    } else {
        // 没有回调的情况
        if(!(REQ_SN_KEY in params) || typeof String(params[REQ_SN_KEY]) !== 'string') {
            handler(params.value)
            return
        }

        let sn = params[REQ_SN_KEY]

        delete params[REQ_SN_KEY]

        let val = handler(params.value)
        let response = {}

        if(typeof val == 'object') {
            if(val instanceof HTMLElement)
                throw Error('HTMLElement instance is not allowed to return to native.')
        }

        response[RES_SN_KEY] = sn
        response['value'] = val

        // native must assign api `*.web.callback` first
        // invokeAppApi('web.callback', response)
        invokeAppApi(api.replace(/\//g, '.'), response)
    }
}

/**
 * Event
 * @param  {String} event
 * @param  {String} str optional
 * @useage:
 * appNotifyWeb('com.companyName.hybridSDK.event.eventName', JSON.stringify({value: 'xxx'}))
*/
function appNotifyWeb(event, str) {
    if(typeof event !== 'string' || (str && typeof str !== 'string')) throw Error('参数不合法，必须是字符串')

    if(str) {
        let params = parseStr(str)
        emit(event, params.value)
    } else {
        emit(event)
    }
}

function parseStr(str) {
    str = decodeURIComponent(str)

    let params

    try {
        params = JSON.parse(str)
    } catch(e) {
        if(/{/.test(str)) throw e
        params = null
    }

    return params
}

/**
 * on('mback', function(data) {})
 */
function on(event, handler) {
    if(!event) return

    let fns = HANDLERS[event]

    if(!isFn(handler)) return false

    if(!handler.name) console.warn(`绑定事件的handler请尽量不用匿名函数！`)

    if(!fns) {
        fns = handler
        // 只通知客户端监听一次
        invokeAppApi(`${event}/listen`)
    } else {
        if(!IS_ARR(fns)) fns = [fns]
        if(IS_ARR(fns) && ~fns.indexOf(handler)) return
        fns.push(handler)
    }

    HANDLERS[event] = fns
}

function emit(event, data) {
    if(!event) {
        if(Hybrid.debug) console.warn('Receive empty event')
        return
    }

    let fns = HANDLERS[event]

    if(IS_ARR(fns)) fns.forEach((fn) => {fn(data)})
    else if(isFn(fns)) fns(data)
}

function off(event, handler) {
    if(!event) return

    invokeAppApi(`${event}/removeListen`)

    if(!(event in HANDLERS)) return

    if(isFn(handler)) {
        let fns = HANDLERS[event]

        if(IS_ARR(fns)) {
            HANDLERS[event] = fns.filter((fn) => {
                return fn != handler
            })

            HANDLERS[event].length !== fns.length
        } else {
            fns == handler ? delete HANDLERS[event] : false
        }
    } else {
        delete HANDLERS[event]
    }
}

function createNamespace(root, names) {
    let i = 0
    let a

    while(a = names[i++]) {
        if(a in root && 'object' !== type(root[a])) throw Error(`${a} 命名空间冲突`)
        if(!(a in root)) root[a] = {}
        root = root[a]
    }

    return root
}

function callbackSign(api, callback, context, nextTick) {
    if(isFn(callback)) {
        // api = api.replace(/\./g, '/')
        let sn = WEB_CB_SN_PREFIX + Date.now()
        let map = CALLBACKS[api] || (CALLBACKS[api] = {})

        // 注意，箭头函数没有arguments！！
        map[sn] = function(value) {
            let args = SLICE.call(arguments)

            if(!nextTick) {
                callback.apply(context, args)
            } else {
                setTimeout(() => {
                    callback.apply(context, args)
                }, 0)
            }

            delete map[sn]
        }

        return sn
    }

    return ''
}

function findCallback(api, sn) {
    // api = api.replace(/\./g, '/')

    return (CALLBACKS[api] || {})[sn]
}

function openURL(url) {
    // for debug in browsers
    if(!Hybrid.isInApp || Hybrid.debug) console.info('Request url: %s\nOrigin url: %s', url, decodeURIComponent(url))

    if(!Hybrid.isInApp) return

    let iframe = document.createElement('iframe')

    // Android必须先赋值再append
    document.body.appendChild(iframe)

    iframe.style.cssText = 'display:none;width:0px;height:0px'
    iframe.src = url
    // iframe.onload = function() {}

    setTimeout(function() {
        iframe.parentNode.removeChild(iframe)
    }, 0)

    return true
}

function freeze(k, v, o = Hybrid) {
    if('__defineGetter__' in o) o.__defineGetter__(k, () => {return isFn(v) ? v() : v})
    else o[k] = isFn(v) ? v() : v
}

function serialize(obj) {
    let r = []

    for(let k in obj) {
        let v = obj[k]

        if(typeof v == 'object') v = JSON.stringify(v)

        r.push(k + '=' + v)
    }

    return r.join('&')
}

function matchArray(str, arr) {
    for(let i=0; i<arr.length; i++) {
        if(new RegExp(arr[i]).test(str)) return arr[i].replace(/\s+/, '')
    }

    return 'Other'
}

function resolveUrl(url) {
    if(!url) return url

    url = url.trim()

    if(!/^http(''|s)?:\/\//.test(url)) {
        let parsed = parseUrl(url)

        url = url.replace(/^(?:\.)?\//,'')
        url = parsed.protocol + '//' + parsed.host + '/' + url
    }

    return url
}

function parseUrl(url) {
    let a = document.createElement('a')

    a.href = (url || 'x.html')

    return {
        protocol: a.protocol.replace(':', ''),
        host: a.host,
        port: a.port,
        pathname: a.pathname,
        search: a.search,
        hash: a.hash
    }
}

function type(o) {
    return Object.prototype.toString.apply(o).slice(8, -1).toLowerCase()
}

function isFn(fn) {
    return type(fn) === 'function'
}

function noop() {
    let args = SLICE.call(arguments)
    let callback = args.length && args[args.length - 1]

    return isFn(callback) ? callback(null) : null
}

module.exports = Hybrid
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter,
  domain = require('domain'),
  path = require('path'),
  util = require('util'),
  Log = require('./log'),
  Router = require('./router'),
  Extend = require('./extend'),
  Render = require('./render'),
  create = require('./create'),
  version = require('../package.json').version,
  env = process.env;

var Hexo = module.exports = function(baseDir, args){
  var config = this.config = {};

  this.base_dir = baseDir;
  this.public_dir = path.join(baseDir, 'public');
  this.source_dir = path.join(baseDir + 'source');
  this.plugin_dir = path.join(baseDir + 'node_modules');
  this.script_dir = path.join(baseDir + 'scripts');

  this.__defineGetter__('theme_dir', function(){
    return path.join(baseDir, 'themes', config.theme);
  });

  this.safe = !!args.save;
  this.debug = !!args.debug;
  this.init = false;

  this.log = new Log({hide: args.debug ? 9 : 7});
  this.route = new Router();
  this.render = new Render(this);

  var extend = this.extend = new Extend();

  [
    'console',
    'deployer',
    'filter',
    'generator',
    'helper',
    'migrator',
    'processor',
    'renderer',
    'swig',
    'tag'
  ].forEach(function(item){
    extend.module(item, require('./extend/' + item));
  });
};

var proto = Hexo.prototype;

proto.__proto__ = EventEmitter.prototype;

proto.core_dir = path.dirname(__dirname) + '/';
proto.lib_dir = __dirname + '/';
proto.env = env;
proto.version = version;
proto.util = util;
proto.create = create;

proto.call = function(name, args, callback){
  if (!callback){
    if (typeof args === 'function'){
      callback = args;
      args = {};
    } else {
      callback = function(){};
    }
  }

  var extend = this.extend.console,
    console = extend.store[name] || extend.alias[name];

  name = name.toLowerCase();

  if (console){
    var d = domain.create();

    d.on('error', function(err){
      callback(err);
    });

    d.run(function(){
      console(args, callback);
    });
  } else {
    callback(new Error('Console `' + name + '` not found'));
  }
};<commit_msg>Fixed core directory path. Dispose domain when a call done. Call chaining.<commit_after>var EventEmitter = require('events').EventEmitter,
  domain = require('domain'),
  path = require('path'),
  util = require('util'),
  Log = require('./log'),
  Router = require('./router'),
  Extend = require('./extend'),
  Render = require('./render'),
  create = require('./create'),
  version = require('../package.json').version,
  env = process.env;

var Hexo = module.exports = function(baseDir, args){
  var config = this.config = {};

  this.base_dir = baseDir;
  this.public_dir = path.join(baseDir, 'public');
  this.source_dir = path.join(baseDir, 'source');
  this.plugin_dir = path.join(baseDir, 'node_modules');
  this.script_dir = path.join(baseDir, 'scripts');

  this.__defineGetter__('theme_dir', function(){
    return path.join(baseDir, 'themes', config.theme);
  });

  this.safe = !!args.save;
  this.debug = !!args.debug;
  this.init = false;

  this.log = new Log({hide: args.debug ? 9 : 7});
  this.route = new Router();
  this.render = new Render(this);

  var extend = this.extend = new Extend();

  [
    'console',
    'deployer',
    'filter',
    'generator',
    'helper',
    'migrator',
    'processor',
    'renderer',
    'swig',
    'tag'
  ].forEach(function(item){
    extend.module(item, require('./extend/' + item));
  });
};

var proto = Hexo.prototype;

proto.__proto__ = EventEmitter.prototype;

proto.core_dir = path.dirname(__dirname) + '/';
proto.lib_dir = __dirname + '/';
proto.env = env;
proto.version = version;
proto.util = util;
proto.create = create;

proto.call = function(name, args, callback){
  if (!callback){
    if (typeof args === 'function'){
      callback = args;
      args = {};
    } else {
      callback = function(){};
    }
  }

  var extend = this.extend.console,
    console = extend.store[name] || extend.alias[name];

  name = name.toLowerCase();

  if (console){
    var d = domain.create();

    d.on('error', function(err){
      d.dispose();
      callback(err);
    });

    d.run(function(){
      console(args, callback);
    });
  } else {
    callback(new Error('Console `' + name + '` not found'));
  }

  return this;
};<|endoftext|>


<commit_before>/* eslint-disable no-case-declarations, no-restricted-modules */

const os = require('os');
const cluster = require('cluster');
const EventEmitter = require('events').EventEmitter;
const oe = require('obj-ease');

let time;
let timeEnd;

class Core extends EventEmitter {
    constructor(config, log) {
        super();
        config = config || {};
        this.log = log;
        this.db = {};
        this.queries = {};
        this.views = {};
        this.viewsEnv = {};
        this.name = config.name || 'meta';
        this.backend = config.backend || 'file';
        this.scriptTimeout = config.scriptTimeout || 10000;

        if (config.verbosity === 'debug') {
            time = console.time;
            timeEnd = console.timeEnd;
        } else {
            time = () => {};
            timeEnd = () => {};
        }

        require('./backend/' + this.backend + '.js')(this);

        this.workers = [];
        this.numWorkers = os.cpus().length;

        cluster.setupMaster({
            exec: 'lib/worker.js',
            args: [],
            silent: false
        });

        cluster.on('online', worker => {
            this.workers.push(worker);
            log.info('worker ' + worker.process.pid + ' is online');
            worker.on('message', msg => {
                switch (msg.type) {
                    case 'view':
                        if (msg.payload === '') {
                            delete this.views[msg.id];
                        } else {
                            this.views[msg.id] = msg.payload;
                        }
                        this.emit('view', msg.id, msg.payload);
                        break;
                    default:
                        console.log(msg);
                }
            });
            if (this.workers.length === this.numWorkers) {
                log.info('all workers ready');
                startInit();
            }
        });

        const env = {};
        if (config.verbosity === 'debug') {
            env.DEBUG = '1';
        }
        for (let i = 0; i < this.numWorkers; i++) {
            cluster.fork(env);
        }

        this.on('update', (id, payload) => {
            this.workers.forEach(worker => {
                time('sending update to worker ' + worker.process.pid);
                worker.send({type: 'update', id, payload, rev: this.rev});
                timeEnd('sending update to worker ' + worker.process.pid);
            });
        });

        this.on('ready', () => {
            startInit();
        });

        let eventCount = 0;
        function startInit() {
            eventCount += 1;
            if (eventCount === 2) {
                init();
            }
        }

        const that = this;

        function init() {
            that.workers.forEach(worker => {
                time('sending db to worker ' + worker.process.pid);
                worker.send({type: 'db', db: that.db, rev: that.rev});
                timeEnd('sending db to worker ' + worker.process.pid);
            });
            Object.keys(that.queries).forEach(id => {
                that.query(id, that.queries[id], true);
            });
            log.info('init complete');
        }
    }

    getRev(id) {
        if (this.db[id] && (typeof this.db[id]._rev !== 'undefined')) {
            const rev = this.db[id]._rev;
            delete this.db[id]._rev;
            return rev;
        }
        return -1;
    }

    setRev(id, rev) {
        if (this.db[id]) {
            this.db[id]._rev = rev;
        }
    }

    incRev(id, rev) {
        if (this.db[id]) {
            this.db[id]._rev = rev + 1;
        }
    }

    set(id, payload) {
        if (typeof payload !== 'object') {
            return false;
        }
        delete payload._rev;
        delete payload._id;
        const rev = this.getRev(id);
        if (this.db[id]) {
            delete this.db[id]._id;
        }
        if (!oe.equal(this.db[id], payload)) {
            this.db[id] = payload;
            this.db[id]._id = id;
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.db[id]._id = id;

        this.setRev(id, rev);
        return false;
    }

    prop(id, payload) {
        let rev;
        switch (payload.method) {
            case 'set':
                rev = this.getRev(id);
                if (oe.setProp(this.db[id], payload.prop, payload.val)) {
                    delete this.db[id]._id;
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                this.setRev(id, rev);
                return false;

            case 'create':
                if (typeof oe.getProp(this.db[id], payload.prop) === 'undefined') {
                    rev = this.getRev(id);
                    oe.setProp(this.db[id], payload.prop, payload.val);
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                return false;

            case 'del':
                rev = this.getRev(id);
                if (oe.delProp(this.db[id], payload.prop)) {
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                this.setRev(id, rev);
                return false;

            default:
                console.log('unknown prop method', payload.method);
        }
    }

    extend(id, payload) {
        payload = payload || {};
        const rev = this.getRev(id);
        delete payload._id;
        delete payload._rev;
        if (this.db[id]) {
            delete this.db[id]._id;
        } else {
            this.db[id] = {};
        }
        const change = oe.extend(this.db[id], payload);
        this.db[id]._id = id;
        if (change) {
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.setRev(id, rev);
        return false;
    }

    del(id) {
        delete this.db[id];
        this.emit('update', id, '');
    }

    getWorker() {
        this.nextWorker = this.nextWorker || 0;
        const id = this.nextWorker;
        this.nextWorker += 1;
        if (this.nextWorker >= this.numWorkers) {
            this.nextWorker = 0;
        }
        return id;
    }

    query(id, payload, init) {
        if (!this.viewsEnv[id]) {
            this.viewsEnv[id] = {};
        }
        let workerId;
        if (!this.viewsEnv[id] || typeof this.viewsEnv[id].worker === 'undefined' || this.viewsEnv[id].worker >= this.numWorkers) {
            workerId = this.getWorker();
            this.viewsEnv[id].worker = workerId;
        } else {
            workerId = this.viewsEnv[id].worker;
        }

        if (payload === '') {
            delete this.queries[id];
            delete this.viewsEnv[id];
            delete this.views[id];
        } else {
            this.queries[id] = payload;
        }

        if (!init) {
            this.emit('query');
        }

        this.log.debug('sending query', id, 'to worker', workerId, payload);
        this.workers[workerId].send({type: 'query', id, payload, init: init ? this.views[id] : undefined});
    }

}

module.exports = Core;
<commit_msg>delete via set<commit_after>/* eslint-disable no-case-declarations, no-restricted-modules */

const os = require('os');
const cluster = require('cluster');
const EventEmitter = require('events').EventEmitter;
const oe = require('obj-ease');

let time;
let timeEnd;

class Core extends EventEmitter {
    constructor(config, log) {
        super();
        config = config || {};
        this.log = log;
        this.db = {};
        this.queries = {};
        this.views = {};
        this.viewsEnv = {};
        this.name = config.name || 'meta';
        this.backend = config.backend || 'file';
        this.scriptTimeout = config.scriptTimeout || 10000;

        if (config.verbosity === 'debug') {
            time = console.time;
            timeEnd = console.timeEnd;
        } else {
            time = () => {};
            timeEnd = () => {};
        }

        require('./backend/' + this.backend + '.js')(this);

        this.workers = [];
        this.numWorkers = os.cpus().length;

        cluster.setupMaster({
            exec: 'lib/worker.js',
            args: [],
            silent: false
        });

        cluster.on('online', worker => {
            this.workers.push(worker);
            log.info('worker ' + worker.process.pid + ' is online');
            worker.on('message', msg => {
                switch (msg.type) {
                    case 'view':
                        if (msg.payload === '') {
                            delete this.views[msg.id];
                        } else {
                            this.views[msg.id] = msg.payload;
                        }
                        this.emit('view', msg.id, msg.payload);
                        break;
                    default:
                        console.log(msg);
                }
            });
            if (this.workers.length === this.numWorkers) {
                log.info('all workers ready');
                startInit();
            }
        });

        const env = {};
        if (config.verbosity === 'debug') {
            env.DEBUG = '1';
        }
        for (let i = 0; i < this.numWorkers; i++) {
            cluster.fork(env);
        }

        this.on('update', (id, payload) => {
            this.workers.forEach(worker => {
                time('sending update to worker ' + worker.process.pid);
                worker.send({type: 'update', id, payload, rev: this.rev});
                timeEnd('sending update to worker ' + worker.process.pid);
            });
        });

        this.on('ready', () => {
            startInit();
        });

        let eventCount = 0;
        function startInit() {
            eventCount += 1;
            if (eventCount === 2) {
                init();
            }
        }

        const that = this;

        function init() {
            that.workers.forEach(worker => {
                time('sending db to worker ' + worker.process.pid);
                worker.send({type: 'db', db: that.db, rev: that.rev});
                timeEnd('sending db to worker ' + worker.process.pid);
            });
            Object.keys(that.queries).forEach(id => {
                that.query(id, that.queries[id], true);
            });
            log.info('init complete');
        }
    }

    getRev(id) {
        if (this.db[id] && (typeof this.db[id]._rev !== 'undefined')) {
            const rev = this.db[id]._rev;
            delete this.db[id]._rev;
            return rev;
        }
        return -1;
    }

    setRev(id, rev) {
        if (this.db[id]) {
            this.db[id]._rev = rev;
        }
    }

    incRev(id, rev) {
        if (this.db[id]) {
            this.db[id]._rev = rev + 1;
        }
    }

    set(id, payload) {
        if (payload === '') {
            return this.del(id);
        }
        if (typeof payload !== 'object') {
            return false;
        }
        delete payload._rev;
        delete payload._id;
        const rev = this.getRev(id);
        if (this.db[id]) {
            delete this.db[id]._id;
        }
        if (!oe.equal(this.db[id], payload)) {
            this.db[id] = payload;
            this.db[id]._id = id;
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.db[id]._id = id;

        this.setRev(id, rev);
        return false;
    }

    prop(id, payload) {
        let rev;
        switch (payload.method) {
            case 'set':
                rev = this.getRev(id);
                if (oe.setProp(this.db[id], payload.prop, payload.val)) {
                    delete this.db[id]._id;
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                this.setRev(id, rev);
                return false;

            case 'create':
                if (typeof oe.getProp(this.db[id], payload.prop) === 'undefined') {
                    rev = this.getRev(id);
                    oe.setProp(this.db[id], payload.prop, payload.val);
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                return false;

            case 'del':
                rev = this.getRev(id);
                if (oe.delProp(this.db[id], payload.prop)) {
                    this.db[id]._id = id;
                    this.incRev(id, rev);
                    this.emit('update', id, this.db[id]);
                    return true;
                }
                this.setRev(id, rev);
                return false;

            default:
                console.log('unknown prop method', payload.method);
        }
    }

    extend(id, payload) {
        if (!payload || typeof payload !== 'object') {
            return false;
        }
        const rev = this.getRev(id);
        delete payload._id;
        delete payload._rev;
        if (this.db[id]) {
            delete this.db[id]._id;
        } else {
            this.db[id] = {};
        }
        const change = oe.extend(this.db[id], payload);
        this.db[id]._id = id;
        if (change) {
            this.incRev(id, rev);
            this.emit('update', id, this.db[id]);
            return true;
        }
        this.setRev(id, rev);
        return false;
    }

    del(id) {
        delete this.db[id];
        this.emit('update', id, '');
    }

    getWorker() {
        this.nextWorker = this.nextWorker || 0;
        const id = this.nextWorker;
        this.nextWorker += 1;
        if (this.nextWorker >= this.numWorkers) {
            this.nextWorker = 0;
        }
        return id;
    }

    query(id, payload, init) {
        if (!this.viewsEnv[id]) {
            this.viewsEnv[id] = {};
        }
        let workerId;
        if (!this.viewsEnv[id] || typeof this.viewsEnv[id].worker === 'undefined' || this.viewsEnv[id].worker >= this.numWorkers) {
            workerId = this.getWorker();
            this.viewsEnv[id].worker = workerId;
        } else {
            workerId = this.viewsEnv[id].worker;
        }

        if (payload === '') {
            delete this.queries[id];
            delete this.viewsEnv[id];
            delete this.views[id];
        } else {
            this.queries[id] = payload;
        }

        if (!init) {
            this.emit('query');
        }

        this.log.debug('sending query', id, 'to worker', workerId, payload);
        this.workers[workerId].send({type: 'query', id, payload, init: init ? this.views[id] : undefined});
    }

}

module.exports = Core;
<|endoftext|>