<commit_before>'use strict';

import stampit from 'stampit'
import Promise from 'bluebird'
import {EventEmitter2 as EventEmitter} from 'eventemitter2'
import cuid from 'cuid'

export default function possum(cfg = {}) {

    const ANY_TRANSITION = 'ANY'

    const EVENTS = {
        INVALID_TRANSITION: 'invalidTransition'
        ,NO_HANDLER: 'noHandler'
        ,HANDLING: 'handling'
        ,HANDLED: 'handled'
        ,INVOKED: 'invoked'
        ,DEFERRED: 'deferred'
        ,TRANSITIONED: 'transitioned'
    }

    cfg.emitterOpts = (cfg.emitterOpts || {
        wildcards: true
        ,delimiter: '.'
        ,newListener: true
        ,maxListeners: 10
    })

    /**
     * possums are event emitters
     * */
    const emittable =  stampit()
        .methods({
            emitEvent: function(e) {
                this.emit(this.namespaced(e.topic), e)
                return this
            }
            ,namespaced: function(value, namespace) {
                namespace = (namespace || this.namespace)
                let pre = ''
                if(namespace) {
                    pre = `${namespace}${cfg.emitterOpts.delimiter}`
                }
                return `${pre}${value}`
            }
        })
        .init(function(){
            //expose emitter
            this.emitter = (cfg.emitter || new EventEmitter(cfg.emitterOpts))

            const eventModel = stampit()
                .refs({
                    topic: undefined
                     , payload: undefined
                     , state: undefined
                     , timestamp: undefined
                     , id: undefined
                     , namespace: undefined
                })
                .init(function() {
                    this.timestamp = new Date().toUTCString()
                    this.id = cuid()
                })

            this.createEvent = eventModel
            this.copyEvent = (e, topic) => {
                return this.createEvent({
                    topic: (topic || e.topic)
                    , payload: e.payload
                    , state: e.state
                    , timestamp: new Date().toUTCString()
                    , namespace: e.namespace
                    , id: cuid()
                })
            }

            const forwardFn = (fn) => {
                this[fn] = this.emitter[fn].bind(this.emitter)
            }

            let eventApi = [
                'addListener'
                ,'on'
                , 'onAny'
                , 'offAny'
                , 'once'
                , 'many'
                , 'removeListener'
                , 'off'
                , 'listeners'
                , 'listenersAny'
                , 'emit'
            ]
            eventApi.forEach(forwardFn)
        })

    /**
     * the possum machine api
     * */
    let apiModel = stampit()
        .compose(emittable)
        .init(function(){
            if(!this.initialState) {
                throw new Error('an `initialState` config is required')
            }
            let target = this

            let invocations = []

            let deferrals = new Map()

            const replay = (deferred, lastResult) =>  {
                if(!deferred.length) {
                    return lastResult
                }
                let next = deferred.shift()
                if(lastResult && lastResult.then) {
                    return lastResult
                        .then(this.handle.bind(this, next.inputType, next.args))
                        .then(function(res){
                            return replay(deferred, res)
                        })
                }
                return replay(deferred, this.handle(next.inputType,next.args))
            }

            const done = (len, completed, result) => {
                //remove the invocation
                if(invocations.length >= len) {
                    invocations.splice(len -1, 1)
                }
                this.emitEvent(completed)
                return result
            }

            this.currentState = this.initialState
            this.priorState = undefined

            this.handle = function(inputType, args) {
                let len = invocations.push({ inputType, args})
                let handler = this.states.get(this.currentState, inputType)
                if(!handler) {
                    let noHandler = this.createEvent({
                        topic: EVENTS.NO_HANDLER
                        , payload: {
                            args: args
                            , inputType: inputType
                        }
                        , namespace: this.namespace
                        , state: this.currentState
                    })
                    this.emitEvent(noHandler)
                    return this
                }
                //create events
                let handling = this.createEvent({
                    topic: EVENTS.HANDLING
                    , payload: {
                        args: args
                        , inputType: inputType
                    }
                    , namespace: this.namespace
                    , state: this.currentState
                })
                let invoked = this.copyEvent(handling, EVENTS.INVOKED)
                let handled = this.copyEvent(handling, EVENTS.HANDLED)

                //do it
                this.emitEvent(handling)
                let result = handler.call(this, args, target)
                this.emitEvent(invoked)

                if(result && result.then) {
                    return result
                        .bind(this)
                        .then(done.bind(this, len, handled))
                }
                return done(len, handled, result)
            }

            this.deferUntilTransition = (toState = ANY_TRANSITION) => {
                let coll = (deferrals.get(toState) || [])
                let invocation = invocations.pop()
                let deferred = this.createEvent({
                    topic: 'deferred'
                    , state: this.currentState
                    , payload: invocation
                    , namespace: this.namespace
                })
                coll.push(invocation)
                deferrals.set(toState, coll)

                this.emitEvent(deferred)
                return this
            }

            const doTransition = (toState, target) => {
                this.priorState = this.currentState
                this.currentState = toState
                let e = this.createEvent({
                    topic: 'transitioned'
                    , payload: {
                        toState: this.currentState
                        , fromState: this.priorState
                    }
                    , state: this.currentState
                    , namespace: this.namespace
                })
                this.emit(this.namespaced(e.topic),e)
                let deferred = (deferrals.get(toState) || [])
                    .concat(deferrals.get(ANY_TRANSITION) || [])
                deferrals.delete(toState)
                deferrals.delete(ANY_TRANSITION)
                return replay(deferred)
            }
            this.transition = function(toState) {
                if(!this.states.has( toState )) {
                    this.emitEvent(this.createEvent({
                        topic: EVENTS.INVALID_TRANSITION
                        , namespace: this.namespace
                        , payload: { toState: toState, fromState: this.currentState}
                        , state: this.currentState
                    }))
                    return this
                }
                // first exit current state
                let exit = this.states.getExit(this.currentState)
                let enter = this.states.getEntry(toState)
                let result = exit.call(this, target )
                let doTransitionBound = doTransition.bind(this, toState, target )
                if(result && result.then) {
                    return result
                        .bind(this)
                        .then(enter)
                        .then(doTransitionBound)
                }
                result = enter.call(this, target )
                if( result && result.then ) {
                    return result.then(doTransitionBound)
                }
                return doTransitionBound()
            }

            this.target = (obj) => {
                if(obj) {
                    return (target = obj)
                }
                return target
            }
        })

    /**
     * represents a single state in config passed by `states`
     * */
    let stateModel = stampit()
        .refs({
            name: undefined
        })
        .init(function(){
            if(!this.name) {
                throw new Error('`name` is required')
            }
            let enter
            let exit
            let handlers = new Map()

            function noop(){}

            this.entry = () => {
                return (enter || noop)
            }
            this.exit = () => {
                return (exit || noop)
            }
            this.get = (inputType) => {
                return handlers.get(inputType)
            }
            this.set = (handlers = {}) => {
                for(let inputType in handlers) {
                    this.handler(inputType,handlers[inputType])
                }
            }
            this.handler = (inputType, fn) => {
                switch(inputType) {
                    case '_enter':
                        enter = fn
                        break;
                    case '_exit':
                        exit = fn
                        break;
                    default:
                        handlers.set(inputType,fn)
                }
                return this
            }
        })

    /**
     * maps state names to their config and exposes
     * api for retrieving and setting them
     * */
    let statesCollection = stampit()
        .init(function(){
            let map = new Map()

            this.set = (states) => {
                for(let stateName in states) {
                    let state = stateModel({ name: stateName})
                    state.set(states[stateName])
                    map.set(stateName, state)
                }
                return this
            }
            this.get = ( stateName, inputType ) => {
                let cfg = map.get(stateName)
                if(!cfg) {
                    return undefined
                }
                let handler = cfg.get(inputType)
                if(!handler) {
                    return undefined
                }
                return handler
            }
            this.getEntry = ( stateName ) => {
                let cfg = map.get(stateName)
                return cfg.entry()
            }
            this.getExit = ( stateName ) => {
                let cfg = map.get(stateName)
                return cfg.exit()
            }
            this.has = ( stateName ) => {
                return map.has(stateName)
            }
        })

    /**
     * the possum builder api
     * */
    const builder = stampit()
        .init(function(){
            let states = statesCollection()
            let target

            /**
             * @param {Object} cfg
             *  @param {String} initialState the initial state of machine
             *  @param {String} [namespace] the namespace for the machine
             *  */
            this.config = (cfg = {}) => {
                apiModel = apiModel.props(cfg)
                return this
            }
            /**
             * configure state handlers
             * */
            this.states = (statesCfg = {}) => {
                states.set(statesCfg)
                return this
            }
            this.methods = (methods) => {
                apiModel  = apiModel.methods(methods)
                return this
            }
            /**
             * initialization functions for machine creation
             * */
            this.init = (...args) => {
                apiModel = apiModel.init(...args)
                return this
            }
            /**
             * the object for state tracking
             * */
            this.target = (obj) => {
                target = obj
                return this
            }
            /**
             * compose the resulting machine with these behaviors
             * */
            this.compose = (...these) => {
                apiModel = apiModel.compose(...these)
                return this
            }
            /**
             * proxy to underlying props method on machine
             * */
            this.props = (obj) => {
                apiModel = apiModel.props( obj )
                return this
            }
            /**
             * proxy to underlying props method on machine
             * */
            this.refs = (obj) => {
                apiModel = apiModel.refs( obj )
                return this
            }

            /**
             * return underlying machine stamp. Note that `target` will
             * not be set on this even if `target` was assigned earlier.
             * Also note that emitter options will not be passed into
             * instances created with this so you that burder is on the caller
             * to pass options if needed.
             * @return {stamp} api model stamp
             * */
            this.stamp = () => {
                return apiModel.props({
                    states: states
                })
            }

            /**
             * compile our config and create machine
             * @return {possum} instance
             * */
            this.build = (args) => {
                let result = apiModel
                    .props({
                        states: states
                    })
                    .create(args, cfg.emitterOpts)
                result.target(target)
                return result
            }

        })

    return builder.create()
}
<commit_msg>make factory a stamp too<commit_after>'use strict';

import stampit from 'stampit'
import Promise from 'bluebird'
import {EventEmitter2 as EventEmitter} from 'eventemitter2'
import cuid from 'cuid'

export default stampit()
.refs({
    emitterOpts: {
        wildcards: true
        ,delimiter: '.'
        ,newListener: true
        ,maxListeners: 10
    }
})
.init(function(){

    const ANY_TRANSITION = 'ANY'

    const EVENTS = {
        INVALID_TRANSITION: 'invalidTransition'
        ,NO_HANDLER: 'noHandler'
        ,HANDLING: 'handling'
        ,HANDLED: 'handled'
        ,INVOKED: 'invoked'
        ,DEFERRED: 'deferred'
        ,TRANSITIONED: 'transitioned'
    }

    const cfg = {
        emitterOpts : (this.emitterOpts)
    }

    /**
     * possums are event emitters
     * */
    const emittable =  stampit()
        .methods({
            emitEvent: function(e) {
                this.emit(this.namespaced(e.topic), e)
                return this
            }
            ,namespaced: function(value, namespace) {
                namespace = (namespace || this.namespace)
                let pre = ''
                if(namespace) {
                    pre = `${namespace}${cfg.emitterOpts.delimiter}`
                }
                return `${pre}${value}`
            }
        })
        .init(function(){
            //expose emitter
            this.emitter = (cfg.emitter || new EventEmitter(cfg.emitterOpts))

            const eventModel = stampit()
                .refs({
                    topic: undefined
                     , payload: undefined
                     , state: undefined
                     , timestamp: undefined
                     , id: undefined
                     , namespace: undefined
                })
                .init(function() {
                    this.timestamp = new Date().toUTCString()
                    this.id = cuid()
                })

            this.createEvent = eventModel
            this.copyEvent = (e, topic) => {
                return this.createEvent({
                    topic: (topic || e.topic)
                    , payload: e.payload
                    , state: e.state
                    , timestamp: new Date().toUTCString()
                    , namespace: e.namespace
                    , id: cuid()
                })
            }

            const forwardFn = (fn) => {
                this[fn] = this.emitter[fn].bind(this.emitter)
            }

            let eventApi = [
                'addListener'
                ,'on'
                , 'onAny'
                , 'offAny'
                , 'once'
                , 'many'
                , 'removeListener'
                , 'off'
                , 'listeners'
                , 'listenersAny'
                , 'emit'
            ]
            eventApi.forEach(forwardFn)
        })

    /**
     * the possum machine api
     * */
    let apiModel = stampit()
        .compose(emittable)
        .init(function(){
            if(!this.initialState) {
                throw new Error('an `initialState` config is required')
            }
            let target = this

            let invocations = []

            let deferrals = new Map()

            const replay = (deferred, lastResult) =>  {
                if(!deferred.length) {
                    return lastResult
                }
                let next = deferred.shift()
                if(lastResult && lastResult.then) {
                    return lastResult
                        .then(this.handle.bind(this, next.inputType, next.args))
                        .then(function(res){
                            return replay(deferred, res)
                        })
                }
                return replay(deferred, this.handle(next.inputType,next.args))
            }

            const done = (len, completed, result) => {
                //remove the invocation
                if(invocations.length >= len) {
                    invocations.splice(len -1, 1)
                }
                this.emitEvent(completed)
                return result
            }

            this.currentState = this.initialState
            this.priorState = undefined

            this.handle = function(inputType, args) {
                let len = invocations.push({ inputType, args})
                let handler = this.states.get(this.currentState, inputType)
                if(!handler) {
                    let noHandler = this.createEvent({
                        topic: EVENTS.NO_HANDLER
                        , payload: {
                            args: args
                            , inputType: inputType
                        }
                        , namespace: this.namespace
                        , state: this.currentState
                    })
                    this.emitEvent(noHandler)
                    return this
                }
                //create events
                let handling = this.createEvent({
                    topic: EVENTS.HANDLING
                    , payload: {
                        args: args
                        , inputType: inputType
                    }
                    , namespace: this.namespace
                    , state: this.currentState
                })
                let invoked = this.copyEvent(handling, EVENTS.INVOKED)
                let handled = this.copyEvent(handling, EVENTS.HANDLED)

                //do it
                this.emitEvent(handling)
                let result = handler.call(this, args, target)
                this.emitEvent(invoked)

                if(result && result.then) {
                    return result
                        .bind(this)
                        .then(done.bind(this, len, handled))
                }
                return done(len, handled, result)
            }

            this.deferUntilTransition = (toState = ANY_TRANSITION) => {
                let coll = (deferrals.get(toState) || [])
                let invocation = invocations.pop()
                let deferred = this.createEvent({
                    topic: 'deferred'
                    , state: this.currentState
                    , payload: invocation
                    , namespace: this.namespace
                })
                coll.push(invocation)
                deferrals.set(toState, coll)

                this.emitEvent(deferred)
                return this
            }

            const doTransition = (toState, target) => {
                this.priorState = this.currentState
                this.currentState = toState
                let e = this.createEvent({
                    topic: 'transitioned'
                    , payload: {
                        toState: this.currentState
                        , fromState: this.priorState
                    }
                    , state: this.currentState
                    , namespace: this.namespace
                })
                this.emit(this.namespaced(e.topic),e)
                let deferred = (deferrals.get(toState) || [])
                    .concat(deferrals.get(ANY_TRANSITION) || [])
                deferrals.delete(toState)
                deferrals.delete(ANY_TRANSITION)
                return replay(deferred)
            }
            this.transition = function(toState) {
                if(!this.states.has( toState )) {
                    this.emitEvent(this.createEvent({
                        topic: EVENTS.INVALID_TRANSITION
                        , namespace: this.namespace
                        , payload: { toState: toState, fromState: this.currentState}
                        , state: this.currentState
                    }))
                    return this
                }
                // first exit current state
                let exit = this.states.getExit(this.currentState)
                let enter = this.states.getEntry(toState)
                let result = exit.call(this, target )
                let doTransitionBound = doTransition.bind(this, toState, target )
                if(result && result.then) {
                    return result
                        .bind(this)
                        .then(enter)
                        .then(doTransitionBound)
                }
                result = enter.call(this, target )
                if( result && result.then ) {
                    return result.then(doTransitionBound)
                }
                return doTransitionBound()
            }

            this.target = (obj) => {
                if(obj) {
                    return (target = obj)
                }
                return target
            }
        })

    /**
     * represents a single state in config passed by `states`
     * */
    let stateModel = stampit()
        .refs({
            name: undefined
        })
        .init(function(){
            if(!this.name) {
                throw new Error('`name` is required')
            }
            let enter
            let exit
            let handlers = new Map()

            function noop(){}

            this.entry = () => {
                return (enter || noop)
            }
            this.exit = () => {
                return (exit || noop)
            }
            this.get = (inputType) => {
                return handlers.get(inputType)
            }
            this.set = (handlers = {}) => {
                for(let inputType in handlers) {
                    this.handler(inputType,handlers[inputType])
                }
            }
            this.handler = (inputType, fn) => {
                switch(inputType) {
                    case '_enter':
                        enter = fn
                        break;
                    case '_exit':
                        exit = fn
                        break;
                    default:
                        handlers.set(inputType,fn)
                }
                return this
            }
        })

    /**
     * maps state names to their config and exposes
     * api for retrieving and setting them
     * */
    let statesCollection = stampit()
        .init(function(){
            let map = new Map()

            this.set = (states) => {
                for(let stateName in states) {
                    let state = stateModel({ name: stateName})
                    state.set(states[stateName])
                    map.set(stateName, state)
                }
                return this
            }
            this.get = ( stateName, inputType ) => {
                let cfg = map.get(stateName)
                if(!cfg) {
                    return undefined
                }
                let handler = cfg.get(inputType)
                if(!handler) {
                    return undefined
                }
                return handler
            }
            this.getEntry = ( stateName ) => {
                let cfg = map.get(stateName)
                return cfg.entry()
            }
            this.getExit = ( stateName ) => {
                let cfg = map.get(stateName)
                return cfg.exit()
            }
            this.has = ( stateName ) => {
                return map.has(stateName)
            }
        })

    /**
     * the possum builder api
     * */
    const builder = stampit()
        .init(function(){
            let states = statesCollection()
            let target

            /**
             * @param {Object} cfg
             *  @param {String} initialState the initial state of machine
             *  @param {String} [namespace] the namespace for the machine
             *  */
            this.config = (cfg = {}) => {
                apiModel = apiModel.props(cfg)
                return this
            }
            /**
             * configure state handlers
             * */
            this.states = (statesCfg = {}) => {
                states.set(statesCfg)
                return this
            }
            this.methods = (methods) => {
                apiModel  = apiModel.methods(methods)
                return this
            }
            /**
             * initialization functions for machine creation
             * */
            this.init = (...args) => {
                apiModel = apiModel.init(...args)
                return this
            }
            /**
             * the object for state tracking
             * */
            this.target = (obj) => {
                target = obj
                return this
            }
            /**
             * compose the resulting machine with these behaviors
             * */
            this.compose = (...these) => {
                apiModel = apiModel.compose(...these)
                return this
            }
            /**
             * proxy to underlying props method on machine
             * */
            this.props = (obj) => {
                apiModel = apiModel.props( obj )
                return this
            }
            /**
             * proxy to underlying props method on machine
             * */
            this.refs = (obj) => {
                apiModel = apiModel.refs( obj )
                return this
            }

            /**
             * return underlying machine stamp. Note that `target` will
             * not be set on this even if `target` was assigned earlier.
             * Also note that emitter options will not be passed into
             * instances created with this so you that burder is on the caller
             * to pass options if needed.
             * @return {stamp} api model stamp
             * */
            this.stamp = () => {
                return apiModel.props({
                    states: states
                })
            }

            /**
             * compile our config and create machine
             * @return {possum} instance
             * */
            this.build = (args) => {
                let result = apiModel
                    .props({
                        states: states
                    })
                    .create(args, cfg.emitterOpts)
                result.target(target)
                return result
            }

        })

    return builder.create()
})
<|endoftext|>


<commit_before>Behaviour.specify("TEXTAREA.codemirror", 'textarea', 0, function(e) {
        var h = e.clientHeight;
        var config = e.getAttribute("codemirror-config") || "";
        config = eval('({'+config+'})');
        var codemirror = CodeMirror.fromTextArea(e,config);
        e.codemirrorObject = codemirror;
        if(typeof(codemirror.getScrollerElement) !== "function") {
            // Maybe older versions of CodeMirror do not provide getScrollerElement method.
            codemirror.getScrollerElement = function(){
                return findElementsBySelector(codemirror.getWrapperElement(), ".CodeMirror-scroll")[0];
            };
        }
        var scroller = codemirror.getScrollerElement();
        scroller.setAttribute("style","border:1px solid black;");
        scroller.style.height = h+"px";

        // the form needs to be populated before the "Apply" button
        e.up('form').on("jenkins:apply",function() {
            e.value = codemirror.getValue()
        })
    });

Behaviour.specify("DIV.textarea-preview-container", 'textarea', 100, function (e) {
        var previewDiv = findElementsBySelector(e,".textarea-preview")[0];
        var showPreview = findElementsBySelector(e,".textarea-show-preview")[0];
        var hidePreview = findElementsBySelector(e,".textarea-hide-preview")[0];
        $(hidePreview).hide();
        $(previewDiv).hide();

        showPreview.onclick = function() {
            // Several TEXTAREAs may exist if CodeMirror is enabled. The first one has reference to the CodeMirror object.
            var textarea = e.parentNode.getElementsByTagName("TEXTAREA")[0];
            var text = textarea.codemirrorObject ? textarea.codemirrorObject.getValue() : textarea.value;
            var render = function(txt) {
                $(hidePreview).show();
                $(previewDiv).show();
                previewDiv.innerHTML = txt;
                layoutUpdateCallback.call();
            };

            new Ajax.Request(rootURL + showPreview.getAttribute("previewEndpoint"), {
                parameters: {
                    text: text
                },
                onSuccess: function(obj) {
                    render(obj.responseText)
                },
                onFailure: function(obj) {
                    render(obj.status + " " + obj.statusText + "
" + obj.responseText)
                }
            });
            return false;
        }

        hidePreview.onclick = function() {
            $(hidePreview).hide();
            $(previewDiv).hide();
        };
});<commit_msg>fixed a javascript error in job configuration page. see https://github.com/jenkinsci/jenkins/commit/2ffece9d737f4a77ed4059c777948299daf22771#commitcomment-3526425<commit_after>Behaviour.specify("TEXTAREA.codemirror", 'textarea', 0, function(e) {
        var h = e.clientHeight;
        var config = e.getAttribute("codemirror-config") || "";
        config = eval('({'+config+'})');
        var codemirror = CodeMirror.fromTextArea(e,config);
        e.codemirrorObject = codemirror;
        if(typeof(codemirror.getScrollerElement) !== "function") {
            // Maybe older versions of CodeMirror do not provide getScrollerElement method.
            codemirror.getScrollerElement = function(){
                return findElementsBySelector(codemirror.getWrapperElement(), ".CodeMirror-scroll")[0];
            };
        }
        var scroller = codemirror.getScrollerElement();
        scroller.setAttribute("style","border:1px solid black;");
        scroller.style.height = h+"px";

        // the form needs to be populated before the "Apply" button
        YAHOO.util.Event.on(e.up('form'), "jenkins:apply", function() {
            e.value = codemirror.getValue()
        })
    });

Behaviour.specify("DIV.textarea-preview-container", 'textarea', 100, function (e) {
        var previewDiv = findElementsBySelector(e,".textarea-preview")[0];
        var showPreview = findElementsBySelector(e,".textarea-show-preview")[0];
        var hidePreview = findElementsBySelector(e,".textarea-hide-preview")[0];
        $(hidePreview).hide();
        $(previewDiv).hide();

        showPreview.onclick = function() {
            // Several TEXTAREAs may exist if CodeMirror is enabled. The first one has reference to the CodeMirror object.
            var textarea = e.parentNode.getElementsByTagName("TEXTAREA")[0];
            var text = textarea.codemirrorObject ? textarea.codemirrorObject.getValue() : textarea.value;
            var render = function(txt) {
                $(hidePreview).show();
                $(previewDiv).show();
                previewDiv.innerHTML = txt;
                layoutUpdateCallback.call();
            };

            new Ajax.Request(rootURL + showPreview.getAttribute("previewEndpoint"), {
                parameters: {
                    text: text
                },
                onSuccess: function(obj) {
                    render(obj.responseText)
                },
                onFailure: function(obj) {
                    render(obj.status + " " + obj.statusText + "
" + obj.responseText)
                }
            });
            return false;
        }

        hidePreview.onclick = function() {
            $(hidePreview).hide();
            $(previewDiv).hide();
        };
});<|endoftext|>


<commit_before>const parallel = require('run-parallel')
const dateFormat = require('dateformat')

function run (config, archiveName) {
  if (!config) {
    return Promise.reject(new Error('A valid configuration object must be passed in.'))
  }
  const sendNativeNotification = require('./nativeNotifications')

  if (config.passphrase) {
    process.env.BORG_PASSPHRASE = config.passphrase
  }
  config.archiveName = archiveName || dateFormat(new Date(), 'yyyy-mm-dd-HHMMss')
  const commands = require('./commands')(config)
  const sendMailNotification = require('./mailer')(config.email)
  const sendPushNotifications = require('./pushNotifications')(config.pushbullet)

  return commands
    .create()
    .then(commands.check)
    .then(commands.prune)
    .then(() => {
      return new Promise(resolve => {
        const notifications = [(cb) => cb()]
        const subject = '👌   [borgjs] backup succesfully executed'
        const message = `Archive ${config.archiveName} created.`
        const emailMessage = {
          subject,
          body: `

${message}

           `
        }
        config.sendSuccessMail && notifications.push((cb) => sendMailNotification(emailMessage, cb))
        config.pushbullet && notifications.push((cb) => sendPushNotifications({subject, message}, cb))
        config.sendSystemNotification && notifications.push((cb) => sendNativeNotification({subject, message}, cb))
        parallel(notifications, resolve)
      })
    })
    .catch(err => {
      return new Promise((resolve, reject) => {
        const notifications = [(cb) => cb()]
        const subject = '💣   [borgjs] error while executing backup'
        const message = `Archive ${config.archiveName} failed. Check logs.`
        const emailMessage = {
          subject,
          body: `

${message}

           `
        }
        config.sendErrorMail && notifications.push((cb) => sendMailNotification(emailMessage, cb))
        config.pushbullet && notifications.push((cb) => sendPushNotifications({subject, message}, cb))
        config.sendSystemNotification && notifications.push((cb) => sendNativeNotification({subject, message}, cb))
        parallel(notifications, reject.bind(null, err))
      })
    })
}

module.exports = run
<commit_msg>feat(lib): Allow to set any environment variable via config<commit_after>const parallel = require('run-parallel')
const dateFormat = require('dateformat')

function run (config, archiveName) {
  if (!config) {
    return Promise.reject(new Error('A valid configuration object must be passed in.'))
  }
  const sendNativeNotification = require('./nativeNotifications')

  if (config.env) {
    for (let key in config.env) {
      process.env[key] = config.env[key]
    }
  }
  config.archiveName = archiveName || dateFormat(new Date(), 'yyyy-mm-dd-HHMMss')
  const commands = require('./commands')(config)
  const sendMailNotification = require('./mailer')(config.email)
  const sendPushNotifications = require('./pushNotifications')(config.pushbullet)

  return commands
    .create()
    .then(commands.check)
    .then(commands.prune)
    .then(() => {
      return new Promise(resolve => {
        const notifications = [(cb) => cb()]
        const subject = '👌   [borgjs] backup succesfully executed'
        const message = `Archive ${config.archiveName} created.`
        const emailMessage = {
          subject,
          body: `

${message}

           `
        }
        config.sendSuccessMail && notifications.push((cb) => sendMailNotification(emailMessage, cb))
        config.pushbullet && notifications.push((cb) => sendPushNotifications({subject, message}, cb))
        config.sendSystemNotification && notifications.push((cb) => sendNativeNotification({subject, message}, cb))
        parallel(notifications, resolve)
      })
    })
    .catch(err => {
      return new Promise((resolve, reject) => {
        const notifications = [(cb) => cb()]
        const subject = '💣   [borgjs] error while executing backup'
        const message = `Archive ${config.archiveName} failed. Check logs.`
        const emailMessage = {
          subject,
          body: `

${message}

           `
        }
        config.sendErrorMail && notifications.push((cb) => sendMailNotification(emailMessage, cb))
        config.pushbullet && notifications.push((cb) => sendPushNotifications({subject, message}, cb))
        config.sendSystemNotification && notifications.push((cb) => sendNativeNotification({subject, message}, cb))
        parallel(notifications, reject.bind(null, err))
      })
    })
}

module.exports = run
<|endoftext|>


<commit_before>(function($) {
	"use strict";

	/**
	 * This namespace provides the models and collections for working with BC web applications and their items.
	 *
	 * @namespace BCAPI.Models.WebApp
	 * @memberOf BCAPI.Models
	 */
	BCAPI.Models.WebApp = {};

    /**
     * This class provides the model for interacting with web apps.
     *
     * ## Load webapps list
     *
     * ```javascript
     * var apps = new BCAPI.Models.WebApp.AppCollection();
     * apps.fetch({
     *		success: function(webAppItems) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItems, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     *
     * apps.each(function(webApp) {
	 * 		// display logic
	 * });
     * ```
     *
     * ## Create webapp
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({
	 * 		"name": "Test app"
	 * });
     *
     * app.save({
	 * 		success: function(webAppItem) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItem, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     * ```
     *
     * If you want to refresh collections which rely on Item model please refresh those collections.
     *
     * ## Remove app
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({name: "Test app"});
     * app.destroy({
	 * 	success: function(webAppItem, response) {
	 * 		// handle success here.
	 *  },
	 *  error: function(webAppItem, xhr, options) {
	 * 		// handle error scenario.
	 *  }
	 * });
     * ```
     *
     * ## Supported attributes
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.Item({
	 *	templateId: -1,
     *  uploadFolder: "images",
     *  requiresApproval: true,
     *  allowFileUpload: true,
     *  customerCanAdd: false,
     *  customerCanDelete: false,
     *  customerCanEdit: false,
     *  anyoneCanEdit: false,
     *  requiresPayment: false,
     *  validDays: -1, // never expire
     *  roleId: 0,
     *  hasAddress: false,
     *  disableDetailPages: false,
     *  locationEnabled: false
     * });
     * ```
     *
     * @class
     */
	BCAPI.Models.WebApp.App = BCAPI.Models.Model.extend({
        idAttribute: 'name',

        /**
         * Set tot true if you want to save or delete an existing item before fetching it
         *
         * @type {boolean}
         * @memberOf WebApp
         */
        isNotNew: null,

        isNew: function() {
            return this.isNotNew ? false : !this.get('id');
        },

        endpoint: function() {
            return '/api/v2/admin/sites/current/webapps';
        },

        fetch: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.fetch.apply(this, arguments);
        },

        destroy: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.destroy.apply(this, arguments);
        }
    });

    /**
     * This class provides a collection that can be used to list all webapps from a site. For
     * more information about a webapp see {@link BCAPI.Models.WebApp.App}.
     *
     * @name AppCollection
     * @class
     * @constructor
     * @memberOf BCAPI.Models.WebApp
     * @example
     * // fetch all available webapps
     * var appCollection = new BCAPI.Models.WebApp.AppCollection();
     *
     * appCollection.fetch({fetchFields: false,
     *  success: function(webapps) {
     *  	webapps.each(function(webapp) {
     *  		// no custom fields are retrieved.
     *  	});
     *  }
     *
     * @example
     * // extract and fetch webap details from a fetched collection (by webapp id).
     * var webappId = 1,
     *		webapp = appCollection.get(webappId);
     *
     * webapp.fetch({
     * 	success: function(webapp) {
     * 		// webapp is now fully loaded.
     *  }
     * });
     *
     * @example
     * // extract and fetch webapp details from fetched collection (by webapp index)
     * var idx = 1,
     * 	    webapp = appCollection.at(idx);
     *
     * webapp.fetch({
     * 	success: function(webapp) {
     * 		// webapp is now fully loaded.
     *  }
     * });
     */
    BCAPI.Models.WebApp.AppCollection = BCAPI.Models.Collection.extend({
        model: BCAPI.Models.WebApp.App,
    	/**
    	 * We override this method in order to transform each returned item into a strong typed
    	 * {@link BCAPI.Models.WebApp.App} models.
    	 *
    	 * @method
    	 * @instance
    	 * @param {Object} response The JSON response received from Web apps api.
    	 * @returns A list of web app items.
    	 * @memberOf BCAPI.Models.WebApp.AppCollection
         */
        parse: function(response) {
        	var webapps = [],
        		self = this;

        	response = BCAPI.Models.Collection.prototype.parse.call(this, response);

        	_.each(response, function(webapp) {
        		webapps.push(self.model(webapp));
        	});

        	return webapps;
        }
    });
})(jQuery);<commit_msg>I fixed a bug into Supported attributes section of App model.<commit_after>(function($) {
	"use strict";

	/**
	 * This namespace provides the models and collections for working with BC web applications and their items.
	 *
	 * @namespace BCAPI.Models.WebApp
	 * @memberOf BCAPI.Models
	 */
	BCAPI.Models.WebApp = {};

    /**
     * This class provides the model for interacting with web apps.
     *
     * ## Load webapps list
     *
     * ```javascript
     * var apps = new BCAPI.Models.WebApp.AppCollection();
     * apps.fetch({
     *		success: function(webAppItems) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItems, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     *
     * apps.each(function(webApp) {
	 * 		// display logic
	 * });
     * ```
     *
     * ## Create webapp
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({
	 * 		"name": "Test app"
	 * });
     *
     * app.save({
	 * 		success: function(webAppItem) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItem, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     * ```
     *
     * If you want to refresh collections which rely on Item model please refresh those collections.
     *
     * ## Remove app
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({name: "Test app"});
     * app.destroy({
	 * 	success: function(webAppItem, response) {
	 * 		// handle success here.
	 *  },
	 *  error: function(webAppItem, xhr, options) {
	 * 		// handle error scenario.
	 *  }
	 * });
     * ```
     *
     * ## Supported attributes
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({
	 *	templateId: -1,
     *  uploadFolder: "images",
     *  requiresApproval: true,
     *  allowFileUpload: true,
     *  customerCanAdd: false,
     *  customerCanDelete: false,
     *  customerCanEdit: false,
     *  anyoneCanEdit: false,
     *  requiresPayment: false,
     *  validDays: -1, // never expire
     *  roleId: 0,
     *  hasAddress: false,
     *  disableDetailPages: false,
     *  locationEnabled: false
     * });
     * ```
     *
     * @class
     */
	BCAPI.Models.WebApp.App = BCAPI.Models.Model.extend({
        idAttribute: 'name',

        /**
         * Set tot true if you want to save or delete an existing item before fetching it
         *
         * @type {boolean}
         * @memberOf WebApp
         */
        isNotNew: null,

        isNew: function() {
            return this.isNotNew ? false : !this.get('id');
        },

        endpoint: function() {
            return '/api/v2/admin/sites/current/webapps';
        },

        fetch: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.fetch.apply(this, arguments);
        },

        destroy: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.destroy.apply(this, arguments);
        }
    });

    /**
     * This class provides a collection that can be used to list all webapps from a site. For
     * more information about a webapp see {@link BCAPI.Models.WebApp.App}.
     *
     * @name AppCollection
     * @class
     * @constructor
     * @memberOf BCAPI.Models.WebApp
     * @example
     * // fetch all available webapps
     * var appCollection = new BCAPI.Models.WebApp.AppCollection();
     *
     * appCollection.fetch({fetchFields: false,
     *  success: function(webapps) {
     *  	webapps.each(function(webapp) {
     *  		// no custom fields are retrieved.
     *  	});
     *  }
     *
     * @example
     * // extract and fetch webap details from a fetched collection (by webapp id).
     * var webappId = 1,
     *		webapp = appCollection.get(webappId);
     *
     * webapp.fetch({
     * 	success: function(webapp) {
     * 		// webapp is now fully loaded.
     *  }
     * });
     *
     * @example
     * // extract and fetch webapp details from fetched collection (by webapp index)
     * var idx = 1,
     * 	    webapp = appCollection.at(idx);
     *
     * webapp.fetch({
     * 	success: function(webapp) {
     * 		// webapp is now fully loaded.
     *  }
     * });
     */
    BCAPI.Models.WebApp.AppCollection = BCAPI.Models.Collection.extend({
        model: BCAPI.Models.WebApp.App,
    	/**
    	 * We override this method in order to transform each returned item into a strong typed
    	 * {@link BCAPI.Models.WebApp.App} models.
    	 *
    	 * @method
    	 * @instance
    	 * @param {Object} response The JSON response received from Web apps api.
    	 * @returns A list of web app items.
    	 * @memberOf BCAPI.Models.WebApp.AppCollection
         */
        parse: function(response) {
        	var webapps = [],
        		self = this;

        	response = BCAPI.Models.Collection.prototype.parse.call(this, response);

        	_.each(response, function(webapp) {
        		webapps.push(self.model(webapp));
        	});

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


<commit_before>var termops = require('./util/termops'),
    feature = require('./util/feature'),
    ops = require('./util/ops'),
    _ = require('underscore'),
    queue = require('queue-async'),
    DEBUG = process.env.DEBUG;

module.exports = index;
module.exports.loadDoc = loadDoc;
module.exports.assignParts = assignParts;
module.exports.generateFrequency = generateFrequency;

// # Index
// @param {Object} source - a Carmen source
// @param {Array} docs - an array of documents
// @param {Function} callback
function index(source, docs, callback) {
    source._geocoder._known = source._geocoder._known || { term:{} };
    var known = source._geocoder._known;

    // First pass over docs.
    // - Creates termsets (one or more arrays of termids) from document text.
    // - Tallies frequency of termids against current frequencies compiling a
    //   final in-memory frequency count of all terms involved with this set of
    //   documents to be indexed.
    // - Stores new frequencies.

    docs = assignParts(docs);
    var freq = generateFrequency(docs);
    var getter = source.getGeocoderData.bind(source);

    // Ensures all shards are loaded.
    var ids = Object.keys(freq).map(function(v) { return parseInt(v, 10); });
    source._geocoder.getall(getter, 'freq', ids, function(err) {
        if (err) return callback(err);
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i];
            freq[id][0] = (source._geocoder.get('freq', id) || [0])[0] + freq[id][0];
            source._geocoder.set('freq', id, freq[id]);
        }
        indexDocs(freq[0], freq, callback);
    });

    // Second pass over docs.
    // - Create term => docid index. Uses calculated frequencies to index only
    //   significant terms for each document.
    // - Create id => grid zxy index.
    function indexDocs(approxdocs, freq, callback) {
        var patch = { grid: {}, term: {}, phrase: {}, degen: {} };
        var features = {};

        for (var i = 0; i < docs.length; i++) loadDoc(docs[i], freq, patch, known);

        var q = queue(500);
        q.defer(function(features, callback) {
            feature.putFeatures(source, docs, function(err) {
                if (err) return callback(err);
                // @TODO manually calls _commit on MBTiles sources.
                // This ensures features are persisted to the store for the
                // next run which would not necessarily be the case without.
                // Given that this is a very performant pattern, commit may
                // be worth making a public function in node-mbtiles (?).
                return source._commit ? source._commit(callback) : callback();
            });
        }, features);
        _(patch).each(setParts);
        q.awaitAll(callback);

        function setParts(data, type) {
            q.defer(function(data, type, callback) {
                var ids = Object.keys(data);
                var cache = source._geocoder;
                cache.getall(getter, type, ids, function(err) {
                    if (err) return callback(err);
                    for (var i = 0; i < ids.length; i++) {
                        var id = ids[i];
                        // This merges new entries on top of old ones.
                        switch (type) {
                        case 'term':
                        case 'grid':
                        case 'degen':
                            var current = cache.get(type, id);
                            if (current) {
                                current.push.apply(current, data[id]);
                            } else {
                                current = data[id];
                            }
                            cache.set(type, id, current);
                            break;
                        case 'phrase':
                            cache.set(type, id, data[id]);
                            break;
                        }
                    }
                    callback();
                });
            }, data, type);
        }
    }
};

function loadDoc(doc, freq, patch, known) {
    doc.doc.id = doc.id;
    doc.hash = termops.feature(doc.id.toString());
    doc.zxy = doc.zxy || [];
    for (var i = 0; i < doc.zxy.length; i++) {
        doc.zxy[i] = ops.zxy(doc.hash, doc.zxy[i]);
    }

    var phrases = doc.phrases;
    var termsets = doc.termsets;
    var termsmaps = doc.termsmaps;

    for (var x = 0; x < phrases.length; x++) {
        var name = phrases[x];
        var terms = termsets[x];
        patch.grid[name] = patch.grid[name] || [];
        patch.grid[name].push.apply(patch.grid[name], doc.zxy);
        patch.phrase[name] = patch.phrase[name] || terms;

        var id;
        var id1 = null;
        var id2 = null;
        var weight1 = 0;
        var weight2 = 0;
        var termsmap = termsmaps[x];

        for (var i = 0; i < terms.length; i++) {
            id = terms[i];
            var weight = Math.log(1 + freq[0][0]/freq[id][0]);
            if (weight > weight1) {
                if (weight1 >= weight2) {
                    weight2 = weight1;
                    id2 = id1;
                }
                weight1 = weight;
                id1 = id;
            } else if (weight > weight2) {
                weight2 = weight;
                id2 = id;
            }

            // This check avoids doing redundant work for a term once
            // it is known to be indexed. @TODO known issue, this prevents
            // degens from being used as an approach to avoiding fnv1a term
            // collisions.
            if (known.term[id]) continue;
            known.term[id] = true;

            // Degenerate terms are indexed for all terms
            // (not just significant ones).
            var degens = termops.degens(termsmap[id]);
            for (var j = 0; j < degens.length; j = j+2) {
                var d = degens[j];
                patch.degen[d] = patch.degen[d] || [];
                patch.degen[d].push(degens[j+1]);
            }
        }

        patch.term[id1] = patch.term[id1] || [];
        patch.term[id1].push(name);
        if (terms.length > 3) {
            patch.term[id2] = patch.term[id2] || [];
            patch.term[id2].push(name);
        }

        // Debug significant term selection.
        if (DEBUG) {
            var debug = termsmap;
            var oldtext = terms.map(function(id) { return debug[id]; }).join(' ');
            var sigtext = terms.length > 3 ?
                debug[id1] + ' ' + debug[id2] :
                debug[id1];
            if (oldtext !== sigtext)  console.log('%s => %s', oldtext, sigtext);
        }
    }
}

function assignParts(docs) {

    for (var i = 0; i < docs.length; i++) {
        var doc = docs[i],
            phrases = [],
            termsets = [],
            termsmaps = [],
            texts = doc.text.split(',');

        for (var x = 0; x < texts.length; x++) {
            var tokens = termops.tokenize(texts[x]);
            if (!tokens.length) continue;
            phrases.push(termops.phrase(tokens));
            termsets.push(termops.terms(tokens));
            termsmaps.push(termops.termsMap(tokens));
        }

        doc.phrases = phrases;
        doc.termsets = termsets;
        doc.termsmaps = termsmaps;
    }

    return docs;
}

function generateFrequency(docs) {
    var freq = {};

    // Uses freq[0] as a convention for storing total # of docs.
    // @TODO determine whether 0 can really ever be a relevant term
    // when using fnv1a.
    freq[0] = [0];
    for (var i = 0; i < docs.length; i++) {
        var termsets = docs[i].termsets;
        for (var j = 0; j < termsets.length; j++) {
            var terms = termsets[j];
            for (var k = 0; k < terms.length; k++) {
                var id = terms[k];
                freq[id] = freq[id] || [0];
                freq[id][0]++;
                freq[0][0]++;
            }
        }
    }

    return freq;
}
<commit_msg>Limit term=>phrase growth during indexing by deduping at key points.<commit_after>var termops = require('./util/termops'),
    feature = require('./util/feature'),
    ops = require('./util/ops'),
    _ = require('underscore'),
    queue = require('queue-async'),
    DEBUG = process.env.DEBUG;

module.exports = index;
module.exports.loadDoc = loadDoc;
module.exports.assignParts = assignParts;
module.exports.generateFrequency = generateFrequency;

// # Index
// @param {Object} source - a Carmen source
// @param {Array} docs - an array of documents
// @param {Function} callback
function index(source, docs, callback) {
    source._geocoder._known = source._geocoder._known || { term:{} };
    var known = source._geocoder._known;

    // First pass over docs.
    // - Creates termsets (one or more arrays of termids) from document text.
    // - Tallies frequency of termids against current frequencies compiling a
    //   final in-memory frequency count of all terms involved with this set of
    //   documents to be indexed.
    // - Stores new frequencies.

    docs = assignParts(docs);
    var freq = generateFrequency(docs);
    var getter = source.getGeocoderData.bind(source);

    // Ensures all shards are loaded.
    var ids = Object.keys(freq).map(function(v) { return parseInt(v, 10); });
    source._geocoder.getall(getter, 'freq', ids, function(err) {
        if (err) return callback(err);
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i];
            freq[id][0] = (source._geocoder.get('freq', id) || [0])[0] + freq[id][0];
            source._geocoder.set('freq', id, freq[id]);
        }
        indexDocs(freq[0], freq, callback);
    });

    // Second pass over docs.
    // - Create term => docid index. Uses calculated frequencies to index only
    //   significant terms for each document.
    // - Create id => grid zxy index.
    function indexDocs(approxdocs, freq, callback) {
        var patch = { grid: {}, term: {}, phrase: {}, degen: {} };
        var features = {};

        for (var i = 0; i < docs.length; i++) loadDoc(docs[i], freq, patch, known);

        var q = queue(500);
        q.defer(function(features, callback) {
            feature.putFeatures(source, docs, function(err) {
                if (err) return callback(err);
                // @TODO manually calls _commit on MBTiles sources.
                // This ensures features are persisted to the store for the
                // next run which would not necessarily be the case without.
                // Given that this is a very performant pattern, commit may
                // be worth making a public function in node-mbtiles (?).
                return source._commit ? source._commit(callback) : callback();
            });
        }, features);
        _(patch).each(setParts);
        q.awaitAll(callback);

        function setParts(data, type) {
            q.defer(function(data, type, callback) {
                var ids = Object.keys(data);
                var cache = source._geocoder;
                cache.getall(getter, type, ids, function(err) {
                    if (err) return callback(err);
                    for (var i = 0; i < ids.length; i++) {
                        var id = ids[i];
                        // This merges new entries on top of old ones.
                        switch (type) {
                        case 'term':
                            var current = cache.get(type, id);
                            if (current) {
                                current.push.apply(current, data[id]);
                                if (current.length > 2000) {
                                    current.sort();
                                    current = _(current).uniq(true);
                                }
                            } else {
                                current = data[id];
                            }
                            cache.set(type, id, current);
                            break;
                        case 'grid':
                        case 'degen':
                            var current = cache.get(type, id);
                            if (current) {
                                current.push.apply(current, data[id]);
                            } else {
                                current = data[id];
                            }
                            cache.set(type, id, current);
                            break;
                        case 'phrase':
                            cache.set(type, id, data[id]);
                            break;
                        }
                    }
                    callback();
                });
            }, data, type);
        }
    }
};

function loadDoc(doc, freq, patch, known) {
    doc.doc.id = doc.id;
    doc.hash = termops.feature(doc.id.toString());
    doc.zxy = doc.zxy || [];
    for (var i = 0; i < doc.zxy.length; i++) {
        doc.zxy[i] = ops.zxy(doc.hash, doc.zxy[i]);
    }

    var phrases = doc.phrases;
    var termsets = doc.termsets;
    var termsmaps = doc.termsmaps;

    for (var x = 0; x < phrases.length; x++) {
        var name = phrases[x];
        var terms = termsets[x];
        patch.grid[name] = patch.grid[name] || [];
        patch.grid[name].push.apply(patch.grid[name], doc.zxy);
        patch.phrase[name] = patch.phrase[name] || terms;

        var id;
        var id1 = null;
        var id2 = null;
        var weight1 = 0;
        var weight2 = 0;
        var termsmap = termsmaps[x];

        for (var i = 0; i < terms.length; i++) {
            id = terms[i];
            var weight = Math.log(1 + freq[0][0]/freq[id][0]);
            if (weight > weight1) {
                if (weight1 >= weight2) {
                    weight2 = weight1;
                    id2 = id1;
                }
                weight1 = weight;
                id1 = id;
            } else if (weight > weight2) {
                weight2 = weight;
                id2 = id;
            }

            // This check avoids doing redundant work for a term once
            // it is known to be indexed. @TODO known issue, this prevents
            // degens from being used as an approach to avoiding fnv1a term
            // collisions.
            if (known.term[id]) continue;
            known.term[id] = true;

            // Degenerate terms are indexed for all terms
            // (not just significant ones).
            var degens = termops.degens(termsmap[id]);
            for (var j = 0; j < degens.length; j = j+2) {
                var d = degens[j];
                patch.degen[d] = patch.degen[d] || [];
                patch.degen[d].push(degens[j+1]);
            }
        }

        patch.term[id1] = patch.term[id1] || [];
        patch.term[id1].push(name);
        if (terms.length > 3) {
            patch.term[id2] = patch.term[id2] || [];
            patch.term[id2].push(name);
        }

        // Debug significant term selection.
        if (DEBUG) {
            var debug = termsmap;
            var oldtext = terms.map(function(id) { return debug[id]; }).join(' ');
            var sigtext = terms.length > 3 ?
                debug[id1] + ' ' + debug[id2] :
                debug[id1];
            if (oldtext !== sigtext)  console.log('%s => %s', oldtext, sigtext);
        }
    }
}

function assignParts(docs) {

    for (var i = 0; i < docs.length; i++) {
        var doc = docs[i],
            phrases = [],
            termsets = [],
            termsmaps = [],
            texts = doc.text.split(',');

        for (var x = 0; x < texts.length; x++) {
            var tokens = termops.tokenize(texts[x]);
            if (!tokens.length) continue;
            phrases.push(termops.phrase(tokens));
            termsets.push(termops.terms(tokens));
            termsmaps.push(termops.termsMap(tokens));
        }

        doc.phrases = phrases;
        doc.termsets = termsets;
        doc.termsmaps = termsmaps;
    }

    return docs;
}

function generateFrequency(docs) {
    var freq = {};

    // Uses freq[0] as a convention for storing total # of docs.
    // @TODO determine whether 0 can really ever be a relevant term
    // when using fnv1a.
    freq[0] = [0];
    for (var i = 0; i < docs.length; i++) {
        var termsets = docs[i].termsets;
        for (var j = 0; j < termsets.length; j++) {
            var terms = termsets[j];
            for (var k = 0; k < terms.length; k++) {
                var id = terms[k];
                freq[id] = freq[id] || [0];
                freq[id][0]++;
                freq[0][0]++;
            }
        }
    }

    return freq;
}
<|endoftext|>


<commit_before>define(
    [
        './widget-extensions',
        './router',
        './events',
        './htmlCanvas',
        'jquery'
    ],

    function (ext, router, events, htmlCanvas, jQuery) {

        /**
         * Base for all widgets. A widget can keep state in variables, contain logic and
         * render itself using `renderOn()`.
         *
         * @example
         *
         *      var titleWidget = function(spec) {
         *          var that = widget(spec);
         *
         *          var title = spec.title;
         *
         *          that.renderContentOn = function(html) {
         *              html.h1('Hello World')
         *          }
         *
         *          return that;
         *      };
         *
         *      var helloWorldWidget = titleWidget({title: 'Hello Widget!'});
         *
         *          $(document).ready(function() {
         *          helloWorldWidget.appendTo('BODY');
         *      });
         *
         * Widgets can also be rendered on a HTML canvas (since widget implements `appendToBrush()`). Eg.
         *
         *      html.div(helloWorldWidget)
         *
         * It is therefor easy to compose widgets from other widgets.
         *
         *
         * @param {Object} spec
         * @param {String} [spec.id] Unique id for widget. Also used for root element when attached/rendered to DOM.
         *                           If not provided an ID will automatically be generated and assigned.
         * @param {Object} [my]
         *
         * @returns {widget}
         */
        var widget = function (spec, my) {
            my = my || {};
            spec = spec || {};

            var that = {};

            var id = spec.id || idGenerator.newId();

            /** Events for widget */
            my.events = events.eventCategory();

            //
            // Public
            //

            /**
             * Returns a unique id for the widget
             *
             * @returns {String}
             */
            that.getId = function () {
                return id;
            };

            /**
             * Performance tasks need for freeing/releasing/cleaning-up resources used by widget.
             *
             * Should always be executed before a widget is disposed. Especially
             * if the widget register events to avoid memory leaks,
             */
            that.dispose = function() {
                my.events.dispose();
            };

            /**
             * Renders the widget on a JQuery / DOM
             *
             * @example
             * widget.appendTo('BODY');
             *
             * @param aJQuery
             */
            that.appendTo = function (aJQuery) {
                that.renderOn(htmlCanvas(aJQuery));
            };

            /**
             * Does the same as appendTo except it first clear the
             * elements matched by aJQuery
             *
             * @param aJQuery
             */
            that.replace = function (aJQuery) {
                var canvas = htmlCanvas(aJQuery);
                canvas.root.asJQuery().empty();
                that.renderOn(canvas);
            };

            /**
             * Answers a jQuery that match the root DOM element. By default
             * by selecting an element that have the same ID as the widget.
             *
             * See 'renderOn'
             *
             * @returns {*}
             */
            that.asJQuery = function () {
                return jQuery('#' + that.getId());
            };

            /**
             * Answers true if if widget have rendered content in DOM
             *
             * @returns {boolean}
             */
            that.isRendered = function () {
                return that.asJQuery().length > 0;
            };

            /**
             * Implementation for `appendToBrush()` to allow a widget to be
             * appended to a brush. See 'htmlCanvas'.
             *
             * Basically it allows us to do:
             *      html.div(widget);
             *
             * @param aTagBrush
             */
            that.appendToBrush = function (aTagBrush) {
                that.appendTo(aTagBrush.asJQuery());
            };

            // Expose events
            that.on = my.events.on;
            that.onceOn = my.events.onceOn;
            that.off = my.events.off;
            that.trigger = my.events.trigger;

            // Protected

            /**
             * Exposes the internal ID generator. Generates new unique IDs to be used
             * for sub-widgets, etc.
             *
             * @returns {String}
             */
            my.nextId = function () {
                return idGenerator.newId();
            };

            my.trigger = my.events.trigger;

            // Route / Controller extensions

            my.linkTo = router.router.linkTo;
            my.linkToPath = router.router.linkToPath;
            my.linkToUrl = router.router.linkToUrl;

            my.redirectTo = router.router.redirectTo;
            my.redirectToPath = router.router.redirectToPath;
            my.redirectToUrl = router.router.redirectToUrl;

            my.getParameters = router.router.getParameters;
            my.getParameter = router.router.getParameter;
            my.setParameters = router.router.setParameters;

            // Render

            /**
             * Main entry point for rendering. For convenience 'renderOn' will  wrap the content
             * rendered by 'renderContentOn' in a root element (renderRootOn) that will be matched
             * by asJQuery.
             *
             * Usually concrete widgets override 'renderContentOn' to render it content. Widgets
             * can override 'renderOn' but must then make sure that it can be matched by 'asJQuery'.
             *
             * One way to do that is to make sure to have only one root element and setting the ID of
             * that element to the ID of the widget.
             *
             * @example
             *
             *      that.renderOn = function (html) {
             *          html.ul({id: that.getId()}
             *              html.li('BMW'),
             *              html.li('Toyota')
             *          );
             *      };
             *
             *
             * @param html
             */
            that.renderOn = function (html) {
                // Renders widget by wrapping `renderContentOn()` in a root element.
                my.renderRootOn(html).render(that.renderContentOn);
            };

            /**
             * Renders a wrapper element (by default a 'widgetjs-widget' tag) and
             * set the element ID to the ID of the widget so that it can be found by
             * 'asJQuery' eg. when we re-render using 'update'.
             *
             * @param html
             * @returns {htmlBrush}
             */
            my.renderRootOn = function (html) {
                return html.tag('widgetjs-widget').id(id);
            };

            /**
             * Overridden in concrete widgets to render widget to canvas/DOM.
             *
             * @example
             *
             *      that.renderContentOn = function (html) {
             *          html.ul(
             *              html.li('BMW'),
             *              html.li('Toyota')
             *          );
             *      };
             *
             * @param {htmlCanvas} html
             */
            that.renderContentOn = function (html) {};

            /**
             * Re-renders the widget and replace it in the DOM
             *
             * Content is first re-rendered on a document fragment. Update then replace the element matched
             * by 'asJQuery' with the new content.
             *
             */
            that.update = function () {
                if (!that.isRendered()) {
                    return;
                }

                // Re-render
                var html = htmlCanvas();
                that.renderOn(html);

                // Replace our self
                that.asJQuery().replaceWith(html.root.element);
            };

            // Third party protected extensions** added to `my`.
            // See widget-extensions.js
            for (var extProperty in ext) {
                if (ext.hasOwnProperty(extProperty)) {
                    my[extProperty] = ext[extProperty];
                }
            }

            return that;
        };

        /**
         * Creates unique ids used by widgets to identify their root div.
         */
        var idGenerator = (function () {
            var that = {};
            var id = 0;

            that.newId = function () {
                id += 1;
                return id.toString();
            };

            return that;
        })();

        return widget;
    }
);
<commit_msg>Fix for failing tests<commit_after>define(
    [
        './widget-extensions',
        './router',
        './events',
        './htmlCanvas',
        'jquery'
    ],

    function (ext, router, events, htmlCanvas, jQuery) {

        /**
         * Base for all widgets. A widget can keep state in variables, contain logic and
         * render itself using `renderOn()`.
         *
         * @example
         *
         *      var titleWidget = function(spec) {
         *          var that = widget(spec);
         *
         *          var title = spec.title;
         *
         *          that.renderContentOn = function(html) {
         *              html.h1('Hello World')
         *          }
         *
         *          return that;
         *      };
         *
         *      var helloWorldWidget = titleWidget({title: 'Hello Widget!'});
         *
         *          $(document).ready(function() {
         *          helloWorldWidget.appendTo('BODY');
         *      });
         *
         * Widgets can also be rendered on a HTML canvas (since widget implements `appendToBrush()`). Eg.
         *
         *      html.div(helloWorldWidget)
         *
         * It is therefor easy to compose widgets from other widgets.
         *
         *
         * @param {Object} spec
         * @param {String} [spec.id] Unique id for widget. Also used for root element when attached/rendered to DOM.
         *                           If not provided an ID will automatically be generated and assigned.
         * @param {Object} [my]
         *
         * @returns {widget}
         */
        var widget = function (spec, my) {
            my = my || {};
            spec = spec || {};

            var that = {};

            var id = spec.id || idGenerator.newId();

            /** Events for widget */
            my.events = events.eventCategory();

            //
            // Public
            //

            /**
             * Returns a unique id for the widget
             *
             * @returns {String}
             */
            that.getId = function () {
                return id;
            };

            that.id = that.getId; //TODO: deprecated

            /**
             * Performance tasks need for freeing/releasing/cleaning-up resources used by widget.
             *
             * Should always be executed before a widget is disposed. Especially
             * if the widget register events to avoid memory leaks,
             */
            that.dispose = function() {
                my.events.dispose();
            };

            /**
             * Renders the widget on a JQuery / DOM
             *
             * @example
             * widget.appendTo('BODY');
             *
             * @param aJQuery
             */
            that.appendTo = function (aJQuery) {
                that.renderOn(htmlCanvas(aJQuery));
            };

            /**
             * Does the same as appendTo except it first clear the
             * elements matched by aJQuery
             *
             * @param aJQuery
             */
            that.replace = function (aJQuery) {
                var canvas = htmlCanvas(aJQuery);
                canvas.root.asJQuery().empty();
                that.renderOn(canvas);
            };

            /**
             * Answers a jQuery that match the root DOM element. By default
             * by selecting an element that have the same ID as the widget.
             *
             * See 'renderOn'
             *
             * @returns {*}
             */
            that.asJQuery = function () {
                return jQuery('#' + that.getId());
            };

            /**
             * Answers true if if widget have rendered content in DOM
             *
             * @returns {boolean}
             */
            that.isRendered = function () {
                return that.asJQuery().length > 0;
            };

            /**
             * Implementation for `appendToBrush()` to allow a widget to be
             * appended to a brush. See 'htmlCanvas'.
             *
             * Basically it allows us to do:
             *      html.div(widget);
             *
             * @param aTagBrush
             */
            that.appendToBrush = function (aTagBrush) {
                that.appendTo(aTagBrush.asJQuery());
            };

            // Expose events
            that.on = my.events.on;
            that.onceOn = my.events.onceOn;
            that.off = my.events.off;
            that.trigger = my.events.trigger;

            // Protected

            /**
             * Exposes the internal ID generator. Generates new unique IDs to be used
             * for sub-widgets, etc.
             *
             * @returns {String}
             */
            my.nextId = function () {
                return idGenerator.newId();
            };

            my.trigger = my.events.trigger;

            // Route / Controller extensions

            my.linkTo = router.router.linkTo;
            my.linkToPath = router.router.linkToPath;
            my.linkToUrl = router.router.linkToUrl;

            my.redirectTo = router.router.redirectTo;
            my.redirectToPath = router.router.redirectToPath;
            my.redirectToUrl = router.router.redirectToUrl;

            my.getParameters = router.router.getParameters;
            my.getParameter = router.router.getParameter;
            my.setParameters = router.router.setParameters;

            // Render

            /**
             * Main entry point for rendering. For convenience 'renderOn' will  wrap the content
             * rendered by 'renderContentOn' in a root element (renderRootOn) that will be matched
             * by asJQuery.
             *
             * Usually concrete widgets override 'renderContentOn' to render it content. Widgets
             * can override 'renderOn' but must then make sure that it can be matched by 'asJQuery'.
             *
             * One way to do that is to make sure to have only one root element and setting the ID of
             * that element to the ID of the widget.
             *
             * @example
             *
             *      that.renderOn = function (html) {
             *          html.ul({id: that.getId()}
             *              html.li('BMW'),
             *              html.li('Toyota')
             *          );
             *      };
             *
             *
             * @param html
             */
            that.renderOn = function (html) {
                // Renders widget by wrapping `renderContentOn()` in a root element.
                my.renderRootOn(html).render(that.renderContentOn);
            };

            /**
             * Renders a wrapper element (by default a 'widgetjs-widget' tag) and
             * set the element ID to the ID of the widget so that it can be found by
             * 'asJQuery' eg. when we re-render using 'update'.
             *
             * @param html
             * @returns {htmlBrush}
             */
            my.renderRootOn = function (html) {
                return html.tag('widgetjs-widget').id(id);
            };

            /**
             * Overridden in concrete widgets to render widget to canvas/DOM.
             *
             * @example
             *
             *      that.renderContentOn = function (html) {
             *          html.ul(
             *              html.li('BMW'),
             *              html.li('Toyota')
             *          );
             *      };
             *
             * @param {htmlCanvas} html
             */
            that.renderContentOn = function (html) {};

            /**
             * Re-renders the widget and replace it in the DOM
             *
             * Content is first re-rendered on a document fragment. Update then replace the element matched
             * by 'asJQuery' with the new content.
             *
             */
            that.update = function () {
                if (!that.isRendered()) {
                    return;
                }

                // Re-render
                var html = htmlCanvas();
                that.renderOn(html);

                // Replace our self
                that.asJQuery().replaceWith(html.root.element);
            };

            // Third party protected extensions** added to `my`.
            // See widget-extensions.js
            for (var extProperty in ext) {
                if (ext.hasOwnProperty(extProperty)) {
                    my[extProperty] = ext[extProperty];
                }
            }

            return that;
        };

        /**
         * Creates unique ids used by widgets to identify their root div.
         */
        var idGenerator = (function () {
            var that = {};
            var id = 0;

            that.newId = function () {
                id += 1;
                return id.toString();
            };

            return that;
        })();

        return widget;
    }
);
<|endoftext|>


<commit_before>'use strict';

const { readdirSync } = require('fs');
const { join } = require('path');

const { parseContentType } = require('./utils.js');

function getInstance(cfg) {
  const headers = cfg.headers;
  const conType = parseContentType(headers['content-type']);
  if (!conType)
    throw new Error('Malformed content type');

  for (const type of TYPES) {
    const matched = type.detect(conType);
    if (!matched)
      continue;

    const instanceCfg = {
      limits: cfg.limits,
      headers,
      conType,
      highWaterMark: undefined,
      fileHwm: undefined,
      defCharset: undefined,
      preservePath: false,
    };
    if (cfg.highWaterMark)
      instanceCfg.highWaterMark = cfg.highWaterMark;
    if (cfg.fileHwm)
      instanceCfg.fileHwm = cfg.fileHwm;
    instanceCfg.defCharset = cfg.defCharset;
    instanceCfg.preservePath = cfg.preservePath;
    return new type(instanceCfg);
  }

  throw new Error(`Unsupported content type: ${headers['content-type']}`);
}

const TYPES = [];
for (const file of readdirSync(join(__dirname, 'types'))) {
  if (/\.js$/i.test(file)) {
    const type = require(`./types/${file}`);
    if (type && typeof type.detect === 'function')
      TYPES.push(type);
  }
}

module.exports = (cfg) => {
  if (typeof cfg !== 'object' || cfg === null)
    cfg = {};

  if (typeof cfg.headers !== 'object'
      || cfg.headers === null
      || typeof cfg.headers['content-type'] !== 'string') {
    throw new Error('Missing Content-Type');
  }

  return getInstance(cfg);
};
<commit_msg>lib: make the module easier to bundle again<commit_after>'use strict';

const { parseContentType } = require('./utils.js');

function getInstance(cfg) {
  const headers = cfg.headers;
  const conType = parseContentType(headers['content-type']);
  if (!conType)
    throw new Error('Malformed content type');

  for (const type of TYPES) {
    const matched = type.detect(conType);
    if (!matched)
      continue;

    const instanceCfg = {
      limits: cfg.limits,
      headers,
      conType,
      highWaterMark: undefined,
      fileHwm: undefined,
      defCharset: undefined,
      preservePath: false,
    };
    if (cfg.highWaterMark)
      instanceCfg.highWaterMark = cfg.highWaterMark;
    if (cfg.fileHwm)
      instanceCfg.fileHwm = cfg.fileHwm;
    instanceCfg.defCharset = cfg.defCharset;
    instanceCfg.preservePath = cfg.preservePath;
    return new type(instanceCfg);
  }

  throw new Error(`Unsupported content type: ${headers['content-type']}`);
}

// Note: types are explicitly listed here for easier bundling
// See: https://github.com/mscdex/busboy/issues/121
const TYPES = [
  require('./types/multipart'),
  require('./types/urlencoded'),
].filter(function(typemod) { return typeof typemod.detect === 'function'; });

module.exports = (cfg) => {
  if (typeof cfg !== 'object' || cfg === null)
    cfg = {};

  if (typeof cfg.headers !== 'object'
      || cfg.headers === null
      || typeof cfg.headers['content-type'] !== 'string') {
    throw new Error('Missing Content-Type');
  }

  return getInstance(cfg);
};
<|endoftext|>


<commit_before>var GitHubApi = require('github');
var ProgressBar = require('progress');
var _ = require('lodash');
var assert = require('assert');
var rateLimit = require('function-rate-limit');
var tracker = require('pivotaltracker');

exports = module.exports = function pivotalToGithub(options, cb) {
  if(!cb) cb = function() {};
  assert(options, 'Missing required parameter `options`');
  assert(options.githubRepo, 'Missing required option `options.githubRepo`');
  assert(options.githubToken, 'Missing required option `options.githubToken`');
  assert(options.githubUser, 'Missing required option `options.githubUser`');
  assert(options.pivotalId, 'Missing required option `options.pivotalId`');
  assert(options.pivotalToken, 'Missing required option `options.pivotalToken`');

  console.log('Fetching Pivotal tickets...');
  exports.getPivotalTickets(options.pivotalToken, options.pivotalId, function(err, tickets) {
    if(err) return cb(err);
    console.log('Got ' + tickets.length + ' tickets from Pivotal.');
    console.log('Creating tickets as GitHub issues on ' + options.githubUser + '/' + options.githubRepo);
    console.log('This will take ' + (2 * tickets.length) + ' seconds.');
    console.log('We\'re conservative about the rate-limit because of GitHub\'s abuse limitting.');
    exports.createGithubTickets(options.githubToken, options.githubRepo, options.githubUser, tickets);
  });
};

exports.getPivotalTickets = function(token, projectId, cb) {
  var client = new tracker.Client(token);
  client.project(projectId).stories.all(function(err, stories) {
    if(err) return cb(err);
    cb(null, stories);
  });
};

exports.createGithubTicket = rateLimit(1, 2000, function(github, repo, user, ticket, cb) {
  function getDescription(ticket) {
    return (ticket.description || '') + '\n\n- - -\n\n' +
      '- **Original Ticket**: ' + ticket.url + '\n' +
      '- **Created by:** ' + ticket.requestedById + '\n' +
      (ticket.ownedById ? ('- **Owned by:** ' + ticket.ownedById + '\n') : '') +
      (ticket.estimate != null ? ('- **Estimate:** ' + ticket.estimate + '\n') : '') +
      (ticket.currentState === 'accepted' ?
        '- **Accepted at:** ' + ticket.acceptedAt : ''
      );
  }

  if(!cb) cb = function() {};

  github.issues.create({
    title: ticket.name,
    body: getDescription(ticket),
    repo: repo,
    user: user,
    labels: [ticket.storyType].concat(_.pluck(ticket.labels, 'name').concat([
      'pivotal'
    ])),
  }, function(err, data) {
    if(err) return cb(err);

    if(ticket.currentState === 'accepted') {
      return github.issues.edit({
        repo: repo,
        user: user,
        number: data.number,
        state: 'closed',
      }, cb);
    }

    cb(null, data);
  });
});

exports.createGithubTickets = function(token, repo, user, tickets, cb) {
  if(!cb) cb = function() {};

  var github = new GitHubApi({
    version: '3.0.0',
    headers: {
      'user-agent': 'Pivotal-To-GitHub'
    }
  });

  github.authenticate({
    type: 'token',
    token: token,
  });

  var pg = new ProgressBar('Tickets to Issues [:current/:total] [|:bar|] :eta remaining', {
    total: tickets.length,
    width: 40,
  });
  tickets.map(function(ticket) {
    exports.createGithubTicket(github, repo, user, ticket, function(err, issue) {
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
      if(err) {
        console.error('ERROR:', err.message);
        console.error('Offending ticket:', JSON.stringify(ticket));
        pg.tick();
        return;
      }

      console.log('Created ticket ' + ticket.id + ' as #' + issue.number);
      pg.tick();
    });
  });
};
<commit_msg>Make several improvements<commit_after>var GitHubApi = require('github');
var ProgressBar = require('progress');
var _ = require('lodash');
var assert = require('assert');
var rateLimit = require('function-rate-limit');
var tracker = require('pivotaltracker');
var async = require('async');

exports = module.exports = function pivotalToGithub(options, cb) {
  if(!cb) cb = function() {};
  assert(options, 'Missing required parameter `options`');
  assert(options.githubRepo, 'Missing required option `options.githubRepo`');
  assert(options.githubToken, 'Missing required option `options.githubToken`');
  assert(options.githubUser, 'Missing required option `options.githubUser`');
  assert(options.pivotalId, 'Missing required option `options.pivotalId`');
  assert(options.pivotalToken, 'Missing required option `options.pivotalToken`');

  console.log('Fetching Pivotal tickets...');
  exports.getPivotalTickets(options.pivotalToken, options.pivotalId, function(err, tickets, people) {
    if(err) return cb(err);
    console.log('--> Got ' + tickets.length + ' tickets from Pivotal.');
    console.log('Creating tickets as GitHub issues on ' + options.githubUser + '/' + options.githubRepo);
    console.log('\n    This will take ' + (2 * tickets.length) + ' seconds.');
    console.log('    We\'re conservative about the rate-limit because of GitHub\'s abuse limitting.\n');
    exports.createGithubTickets(options.githubToken, options.githubRepo, options.githubUser, tickets, people);
  });
};

exports.getPivotalTickets = function(token, projectId, cb) {
  var client = new tracker.Client(token);
  console.log('--> Fetching stories...');
  client.project(projectId).stories.all(function(err, stories) {
    if(err) return cb(err);
    console.log('--> Fetching tasks and comments...');
    async.map(stories, function(story, cb) {
      client.project(projectId).story(story.id).comments.all(function(err, comments) {
        if(err) return cb(err);

        client.project(projectId).story(story.id).tasks.all(function(err, tasks) {
          if(err) return cb(err);
          story.comments = comments;
          story.tasks = tasks;
          cb(err, story);
        });
      });
    }, function(err, stories) {
      console.log('--> Fetching people...');
      client.project(projectId).memberships.all(function(err, people) {
        cb(err, stories, _(people).pluck('person').indexBy('id').value());
      });
    });
  });
};

exports.createGithubTicket = rateLimit(1, 2000, function(github, repo, user, ticket, people, cb) {
  //console.log(people); process.exit(1);
  function getComments(ticket) {
    return '### Comments\n\n' + ticket.comments.map(function(comment) {
      var commenter = people[comment.personId] ? people[comment.personId].name : comment.personId;
      return '[' + comment.createdAt + '] **' + commenter + '**: ' + comment.text + '\n';
    }).join('\n');
  }

  function getTasks(ticket) {
    return '### Tasks\n\n' + _.sortBy(ticket.tasks, 'position').map(function(task) {
      return '- [' + (task.complete ? 'x' : ' ') + '] ' + task.description;
    }).join('\n');
  }

  function getDescription(ticket) {
    var requester = people[ticket.requestedById] ? people[ticket.requestedById].name : ticket.requestedById;
    var owner = people[ticket.ownedById] ? people[ticket.ownedById].name : ticket.ownedById;
    return (ticket.description || '') + '\n\n- - -\n\n' +
      '- **Original Ticket**: ' + ticket.url + '\n' +
      '- **Created by:** ' + requester + '\n' +
      (ticket.ownedById ? ('- **Owned by:** ' + owner + '\n') : '') +
      (ticket.estimate != null ? ('- **Estimate:** ' + ticket.estimate + '\n') : '') +
      (ticket.currentState === 'accepted' ?
        '- **Accepted at:** ' + ticket.acceptedAt + '\n' : '') +
      '- **State:** ' + ticket.currentState +
      (ticket.comments.length ? '\n\n- - -\n\n' + getComments(ticket) : '') +
      (ticket.tasks.length ?  '\n\n- - -\n\n' + getTasks(ticket) : '');
  }

  if(!cb) cb = function() {};

  github.issues.create({
    title: ticket.name,
    body: getDescription(ticket),
    repo: repo,
    user: user,
    labels: [ticket.storyType].concat(_.pluck(ticket.labels, 'name').concat([
      'pivotal'
    ])),
  }, function(err, data) {
    if(err) return cb(err);

    if(ticket.currentState === 'accepted') {
      return github.issues.edit({
        repo: repo,
        user: user,
        number: data.number,
        state: 'closed',
      }, cb);
    }

    cb(null, data);
  });
});

exports.createGithubTickets = function(token, repo, user, tickets, people, cb) {
  if(!cb) cb = function() {};

  var github = new GitHubApi({
    version: '3.0.0',
    headers: {
      'user-agent': 'Pivotal-To-GitHub'
    }
  });

  github.authenticate({
    type: 'token',
    token: token,
  });

  var pg = new ProgressBar('Tickets to Issues [:current/:total] [|:bar|] :eta remaining', {
    total: tickets.length,
    width: 40,
  });

  tickets.map(function(ticket) {
    exports.createGithubTicket(github, repo, user, ticket, people, function(err, issue) {
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
      if(err) {
        console.error('ERROR:', err.message);
        console.error('Offending ticket:', JSON.stringify(ticket));
        pg.tick();
        return;
      }

      console.log('--> Created ticket ' + ticket.id + ' as #' + issue.number);
      pg.tick();
    });
  });
};
<|endoftext|>