<commit_before>/**
 * Created by daniel.irwin on 6/25/17.
 */
'use strict';
module.exports = function Pipeline(opts, tasks){

    return function go(context, responses, final) {
        let timeout = null;

        let clearResponses = Object.keys(responses).reduce((acc, key) => {
            acc[key] =  function cR(){
                if(timeout){
                    clearTimeout(timeout);
                }
                responses[key].apply(this, arguments);
            };
            return acc;
        }, {});

        function nextPlugin() {

            if(timeout){
                clearTimeout(timeout);
            }

            let task = tasks.shift();
            if (!task) {
                final(context);
            }
            else if (task && typeof task === 'function') {
                if(opts.timeout){
                    timeout = setTimeout(function timedOut(){
                        if(opts.continueOnTimeout) {
                            nextPlugin();
                        }
                        else {
                            final(context);
                        }
                    }, opts.timeout);
                }
                task(context, clearResponses, nextPlugin);
            }
        }

        nextPlugin();
    };

};<commit_msg>give pipeline a timeout no matter what atleast for log purposes<commit_after>/**
 * Created by daniel.irwin on 6/25/17.
 */
'use strict';
module.exports = function Pipeline(opts, tasks){
    const DEFAULT_TIMEOUT = 30000;
    const logger = require('./logger');
    const LOG = new logger('Arupex-Pipeline');

    return function go(context, responses, final) {
        let timeout = null;

        let clearResponses = Object.keys(responses).reduce((acc, key) => {
            acc[key] =  function cR(){
                if(timeout){
                    clearTimeout(timeout);
                }
                responses[key].apply(this, arguments);
            };
            return acc;
        }, {});

        function nextPlugin() {

            if(timeout){
                clearTimeout(timeout);
            }

            let task = tasks.shift();
            if (!task) {
                final(context);
            }
            else if (task && typeof task === 'function') {
                if(opts.timeout){
                    timeout = setTimeout(function timedOut(){
                        LOG.warn('your middleware appears to be taking its time', task);
                        if(opts.timeout) {
                            if (typeof opts.timeoutFnc !== 'function') {
                                if (opts.continueOnTimeout) {
                                    nextPlugin();
                                }
                                else {
                                    final(context);
                                }
                            }
                            else {
                                opts.timeoutFnc(context, responses);
                            }
                        }
                    }, opts.timeout || DEFAULT_TIMEOUT);
                }
                task(context, clearResponses, nextPlugin);
            }
        }

        nextPlugin();
    };

};<|endoftext|>


<commit_before>'use strict'

/**
 * Module dependencies
 */
var bind = require('lodash.bind')
var every = require('lodash.every')
var slice = require('lodash.slice')
var indexOf = require('lodash.indexof')
var Injector = require('insider')
var toArray = require('lodash.toarray')
var isArray = require('lodash.isarray')

/**
 * Pipeline constructor. Pipeline is the place where you define a series of
 * operations you need to do when certain things happened (events).
 *
 * @param  {SuperPipe} [superpipe]  Instance of SuperPipe.
 * @return {Pipeline}               Instance of Pipeline.
 */
var Pipeline = module.exports = function Pipeline(superpipe) {
  var pipeline = assign(function() {
    pipeline.toPipe()()
  }, Pipeline.prototype)

  pipeline.pipes = []
  pipeline.connect(superpipe)

  return pipeline
}

Pipeline.prototype.connect = function(superpipe) {
  if (superpipe && superpipe.instanceOfSuperPipe) {
    this.superpipe = superpipe
    this.injector = superpipe.injector
  } else if (superpipe && superpipe instanceof Injector) {
    this.injector = superpipe
  } else {
    this.injector = new Injector()
  }

  return this
}

/**
 * Listen to a event emits from the emitter.
 *
 * @param  {EventEmitter} [emitter] The emitter to listen events from.
 * The superpipe instance will be used as emitter if only event name is provided.
 * @param  {String}       name      Name of the event to listen.
 * @return {Pipeline}               Instance of this Pipeline.
 */
Pipeline.prototype.listenTo = function(emitter, name) {
  function listenTo(eventEmitter, eventName, pipeline) {
    var listenFn = eventEmitter.on || eventEmitter.addEventListener || eventEmitter.addListener
    if ('function' !== typeof listenFn) {
      throw new Error('emitter has no listening funciton "on, addEventListener or addListener"')
    }
    listenFn = bind(listenFn, eventEmitter)
    listenFn(eventName, Pipeline.toPipe(pipeline))
  }

  if ('string' === typeof emitter) {
    var superpipe = this.superpipe
    if (name) {
      // listenTo('nameOfEmitter', 'some event')
      var self = this
      var emitterName = emitter
      emitter = superpipe.getDep(emitterName)
      if (!emitter) {
        superpipe.onSetDep(emitterName, function(newEmitter, oldEmitter) {
          if (oldEmitter) {
            var removeAll = (oldEmitter.removeAllListeners || oldEmitter.off)
            removeAll && removeAll.call(oldEmitter, name)
          }
          listenTo(newEmitter, name, self)
        })
        return this
      }
    } else {
      // listenTo('some event')
      name = emitter
      emitter = superpipe
    }
  }

  listenTo(emitter, name, this)
  return this
}

/**
 * Put function and its dependencies to the pipeline.
 *
 * @param  {Function|String|Number|Array} fn
 *         - Function: The pipe function
 *         - String: Name of function which could be found in dependencies.
 *         - Number: Number of miliseconds to throttle the pipeline.
 *         - Array: A array which contains both `fn` (first) and `deps` (rest).
 * @param  {Array|String}   ...deps String or array of names of dependencies.
 * @return {Pipeline}       Instance of this Pipeline.
 */
Pipeline.prototype.pipe = function(fn, deps, supplies) {
  var type = typeof fn

  if ('number' === typeof fn) {
    return this.throttle(fn)
  } else if ('string' === type) {
    if ('emit' === fn) {
      // .pipe('emit', 'event name', ['event', 'object'])
      return this.emit(deps, supplies)
    } else {
      return this.dipe(fn, deps, supplies)
    }
  } else if (fn instanceof Pipeline) {
    fn = fn.toPipe()
  }

  // save to the pipes array as a pipes unit
  var pipe = this.buildPipe(fn, deps, supplies)
  this.push(pipe)
  return this
}

Pipeline.prototype.emit = function(eventName, deps) {
  var self = this
  var fn = function() {
    var args = toArray(arguments)
    args.unshift(eventName)
    var emit = self.injector.get('emit')
    emit.apply(null, args)
  }

  // Build the pipe.
  var pipe = this.buildPipe(fn, deps)
  this.push(pipe)

  return this
}

Pipeline.prototype.dipe = function(name, deps, supplies) {
  var fn = function() {
    var ofn = pipe.ofn
    if ('function' === typeof ofn) {
      // when it's a function call it with rest of the arguments
      return ofn.apply(this, toArray(arguments))
    } else if ('boolean' === typeof ofn) {
      // directly return the value when it is a boolean for flow control
      return ofn
    } else if (true === pipe.optional && 'undefined' === typeof ofn) {
      // Optional pipe which pipe function can not be found.
      // Return true to ignore and go next.
      return true
    } else {
      throw new Error('Dependency ' + name + ' is not a function or boolean.')
    }
  }

  // Build the pipe.
  var pipe = this.buildPipe(fn, deps, supplies)

  if (/\?$/.test(name)) {
    // Optional pipe if function name ends with question mark.
    pipe.optional = true
    // Remove the trailing question mark.
    name = name.slice(0, name.length - 1)
  }

  // Set the original function name to the pipe object for later dependency discovery.
  pipe.fnName = name
  this.push(pipe)

  return this
}

Pipeline.prototype.throttle = function(num) {
  // generate a throttle function and push to pipes
  var timestamp
  this.push({
    fn: function throttleFn() {
      var now = new Date()
      if (!timestamp || now - timestamp > num) {
        timestamp = new Date()
        return true
      } else {
        return false
      }
    }
  })

  return this
}

Pipeline.prototype.push = function(pipe) {
  if (this.sealed) {
    throw new Error('Pipeline line already sealed, no more pipe could be added.')
  }
  this.pipes.push(pipe)
}

/**
 * The actual function for building a pipe.
 *
 * @param  {Function} fn
 *         - Function: The pipe function
 *         - String: Name of function which could be found in dependencies.
 *         - Number: Number of miliseconds to throttle the pipeline.
 *         - Array: A array which contains both `fn` (first) and `deps` (rest).
 * @param  {Array|String}   deps... String or array of names of dependencies.
 * @return {Object}         An object contains dependencies injected function and deps.
 */
Pipeline.prototype.buildPipe = function(fn, deps, supplies) {
  if ('function' !== typeof fn) {
    throw new Error('fn should be a function, got ' + typeof fn)
  }

  if ('string' === typeof deps) {
    deps = [deps]
  }

  if ('string' === typeof supplies) {
    supplies = [supplies]
  }

  if (deps && !isArray(deps)) {
    throw new Error('deps should be either string or array of dependency names')
  }

  if (supplies && !isArray(supplies)) {
    throw new Error('deps should be either string or array of dependency names')
  }

  if (isArray(deps)) {
    var firstDep = deps[0]
    if ('number' === typeof firstDep && firstDep > 0) {
      deps[0] = null
      while (firstDep > 1) {
        deps.unshift(null)
        firstDep--
      }
    }
  }

  // get our injected version of pipe function
  var injectedFn = this.injector.inject(fn, deps)

  // return injectable function with depedencies array
  return {
    fn: injectedFn,
    ofn: fn,
    deps: deps,
    supplies: supplies
  }
}

Pipeline.prototype.slice = function(begin, end) {
  var sliced = new Pipeline(this.superpipe)
  sliced.pipes = slice(this.pipes, begin, end)

  return sliced
}

Pipeline.prototype.concat = function(pipeline, begin, end) {
  var concated = this.slice()
  concated.pipes = concated.pipes.concat(slice(pipeline.pipes, begin, end))

  return concated
}

Pipeline.prototype.toPipe = function(begin, end) {
  return Pipeline.toPipe(this, slice(this.pipes, begin, end), this.superpipe)
}

/**
 * Set the error handler for this pipeline.
 *
 * @param  {Function|String} fn
 *         - Function: The pipe function
 *         - String: Name of function which could be found in dependencies.
 * @param  {Array|String}   deps... String or array of names of dependencies.
 * @return {Pipeline}       Instance of this Pipeline.
 */
Pipeline.prototype.error = function(fn, deps) {
  if ('number' === typeof fn) {
    throw new Error('Error handler does not accept numeric argument.')
  }
  this.errorHandler = this.pipe(fn, deps).pipes.pop()
  return this
}

Pipeline.toPipe = function(pipeline, pipes, superpipe) {
  return function() {
    if (pipes[0]) {
      var step = 0
      var args = toArray(arguments)
      var injector = new Injector(pipeline.injector)

      var setDep = bind(superpipe.setDep, {
        injector: injector
      })

      // global deps setter for sharing states cross pipelines
      injector.set('set', function() {
        superpipe.setDep.apply(superpipe, arguments)
        setDep.apply(null, arguments)
      })

      // local deps setter unique for each pipeline
      injector.set('setDep', setDep)

      var next = function(err) {
        var pipe
        if (err) {
          if (pipeline.errorHandler) {
            // error handler fn as a pipe
            pipe = pipeline.errorHandler
            // assign the err object to the first argument
            // so the error handler can get the error later
            args[0] = err
          } else {
            console.warn('No error handler function')
            console.error(err)
          }
        } else {
          // get the right unit from the pipes array
          pipe = pipes[step++]
        }

        if (pipe) {
          var fn = pipe.fn
          var deps = pipe.deps
          var fnName = pipe.fnName
          var result
          var resultReturned = false
          var supplies = pipe.supplies
          // No deps or has deps but next is not required as dependency.
          var autoNext = !deps || (indexOf(deps, 'next') === -1)

          // Get the pipe function demanded from dependency container.
          if ('string' === typeof fnName) {
            // Set it as the original pipe function
            pipe.ofn = injector.get(fnName)
          }

          if (pipe.optional) {
            // Optional pipe, go next if we can't get all dependencies.
            var _deps = injector.getAll(deps)
            if (_deps.indexOf(undefined) > -1) {
              next()
              return
            }
          }

          var modifiedSetDep
          var modifySetDep = supplies && supplies.length > 0
          if (modifySetDep) {
            // Modify the `setDep`
            var fulfilled = autoNext && []
            // We will handle the auto next behaviour.
            autoNext = false

            var inSupplies
            injector.set('setDep', function(name, dep, props) {
              var normalized = superpipe.normalizeSetDepArgs(name, dep, props)
              var _props = normalized.props || (normalized.name ? [normalized.name] : [])

              // Whether dependency being injected is defined in `supplies`.
              inSupplies = every(_props, function(propName) {
                if (-1 === indexOf(supplies, propName)) {
                  next('Dependency "' + propName + '" is not defined in supplies.')
                  return false
                }
                if (fulfilled && -1 === indexOf(fulfilled, propName)) {
                  fulfilled.push(propName)
                }
                return true
              })

              if (!inSupplies) {
                return
              }

              // Set the dependencies.
              setDep(name, dep, props)

              if (fulfilled && fulfilled.length === supplies.length) {
                // Set the auto next to true or call next when all required
                // supplies are fulfilled.
                if ('undefined' === typeof result) {
                  // Result is undefined:
                  if (resultReturned) {
                    // The undefined value has been returned by function.
                    // Call next.
                    next()
                  } else {
                    // Function has not been returned, set autoNext to true so
                    // the code below can handle the auto next.
                    autoNext = true
                  }
                } else if (false !== result) {
                  // Function has been returned (async) and it's not false.
                  // Go next.
                  next()
                }
              } else if (fulfilled && fulfilled.length > supplies.length) {
                next('Get more dependencies than what supplies required.')
              }
            })
          }

          // Run the actual function
          result = fn.apply(injector, args)
          resultReturned = true

          if (modifySetDep) {
            // Put back the original `setDep` function.
            injector.set('setDep', setDep)
          }

          // check if we need to run next automatically
          if (autoNext && !err && result !== false) {
            // call next pipe when the returned value is not false
            next()
          }
        }
      }
      // register `next` function as dependency
      injector.set('next', next)
      // start executing the chain
      next()
    }
  }
}
<commit_msg>Rename num to `msec`<commit_after>'use strict'

/**
 * Module dependencies
 */
var bind = require('lodash.bind')
var every = require('lodash.every')
var slice = require('lodash.slice')
var indexOf = require('lodash.indexof')
var Injector = require('insider')
var toArray = require('lodash.toarray')
var isArray = require('lodash.isarray')

/**
 * Pipeline constructor. Pipeline is the place where you define a series of
 * operations you need to do when certain things happened (events).
 *
 * @param  {SuperPipe} [superpipe]  Instance of SuperPipe.
 * @return {Pipeline}               Instance of Pipeline.
 */
var Pipeline = module.exports = function Pipeline(superpipe) {
  var pipeline = assign(function() {
    pipeline.toPipe()()
  }, Pipeline.prototype)

  pipeline.pipes = []
  pipeline.connect(superpipe)

  return pipeline
}

Pipeline.prototype.connect = function(superpipe) {
  if (superpipe && superpipe.instanceOfSuperPipe) {
    this.superpipe = superpipe
    this.injector = superpipe.injector
  } else if (superpipe && superpipe instanceof Injector) {
    this.injector = superpipe
  } else {
    this.injector = new Injector()
  }

  return this
}

/**
 * Listen to a event emits from the emitter.
 *
 * @param  {EventEmitter} [emitter] The emitter to listen events from.
 * The superpipe instance will be used as emitter if only event name is provided.
 * @param  {String}       name      Name of the event to listen.
 * @return {Pipeline}               Instance of this Pipeline.
 */
Pipeline.prototype.listenTo = function(emitter, name) {
  function listenTo(eventEmitter, eventName, pipeline) {
    var listenFn = eventEmitter.on || eventEmitter.addEventListener || eventEmitter.addListener
    if ('function' !== typeof listenFn) {
      throw new Error('emitter has no listening funciton "on, addEventListener or addListener"')
    }
    listenFn = bind(listenFn, eventEmitter)
    listenFn(eventName, Pipeline.toPipe(pipeline))
  }

  if ('string' === typeof emitter) {
    var superpipe = this.superpipe
    if (name) {
      // listenTo('nameOfEmitter', 'some event')
      var self = this
      var emitterName = emitter
      emitter = superpipe.getDep(emitterName)
      if (!emitter) {
        superpipe.onSetDep(emitterName, function(newEmitter, oldEmitter) {
          if (oldEmitter) {
            var removeAll = (oldEmitter.removeAllListeners || oldEmitter.off)
            removeAll && removeAll.call(oldEmitter, name)
          }
          listenTo(newEmitter, name, self)
        })
        return this
      }
    } else {
      // listenTo('some event')
      name = emitter
      emitter = superpipe
    }
  }

  listenTo(emitter, name, this)
  return this
}

/**
 * Put function and its dependencies to the pipeline.
 *
 * @param  {Function|String|Number|Array} fn
 *         - Function: The pipe function
 *         - String: Name of function which could be found in dependencies.
 *         - Number: Number of miliseconds to throttle the pipeline.
 *         - Array: A array which contains both `fn` (first) and `deps` (rest).
 * @param  {Array|String}   ...deps String or array of names of dependencies.
 * @return {Pipeline}       Instance of this Pipeline.
 */
Pipeline.prototype.pipe = function(fn, deps, supplies) {
  var type = typeof fn

  if ('number' === typeof fn) {
    return this.throttle(fn)
  } else if ('string' === type) {
    if ('emit' === fn) {
      // .pipe('emit', 'event name', ['event', 'object'])
      return this.emit(deps, supplies)
    } else {
      return this.dipe(fn, deps, supplies)
    }
  } else if (fn instanceof Pipeline) {
    fn = fn.toPipe()
  }

  // save to the pipes array as a pipes unit
  var pipe = this.buildPipe(fn, deps, supplies)
  this.push(pipe)
  return this
}

Pipeline.prototype.emit = function(eventName, deps) {
  var self = this
  var fn = function() {
    var args = toArray(arguments)
    args.unshift(eventName)
    var emit = self.injector.get('emit')
    emit.apply(null, args)
  }

  // Build the pipe.
  var pipe = this.buildPipe(fn, deps)
  this.push(pipe)

  return this
}

Pipeline.prototype.dipe = function(name, deps, supplies) {
  var fn = function() {
    var ofn = pipe.ofn
    if ('function' === typeof ofn) {
      // when it's a function call it with rest of the arguments
      return ofn.apply(this, toArray(arguments))
    } else if ('boolean' === typeof ofn) {
      // directly return the value when it is a boolean for flow control
      return ofn
    } else if (true === pipe.optional && 'undefined' === typeof ofn) {
      // Optional pipe which pipe function can not be found.
      // Return true to ignore and go next.
      return true
    } else {
      throw new Error('Dependency ' + name + ' is not a function or boolean.')
    }
  }

  // Build the pipe.
  var pipe = this.buildPipe(fn, deps, supplies)

  if (/\?$/.test(name)) {
    // Optional pipe if function name ends with question mark.
    pipe.optional = true
    // Remove the trailing question mark.
    name = name.slice(0, name.length - 1)
  }

  // Set the original function name to the pipe object for later dependency discovery.
  pipe.fnName = name
  this.push(pipe)

  return this
}

Pipeline.prototype.throttle = function(msec) {
  // Generate a throttle function and push to pipes
  var timestamp
  this.push({
    fn: function throttleFn() {
      var now = new Date()
      if (!timestamp || now - timestamp > msec) {
        timestamp = new Date()
        return true
      } else {
        return false
      }
    }
  })

  return this
}

Pipeline.prototype.push = function(pipe) {
  if (this.sealed) {
    throw new Error('Pipeline line already sealed, no more pipe could be added.')
  }
  this.pipes.push(pipe)
}

/**
 * The actual function for building a pipe.
 *
 * @param  {Function} fn
 *         - Function: The pipe function
 *         - String: Name of function which could be found in dependencies.
 *         - Number: Number of miliseconds to throttle the pipeline.
 *         - Array: A array which contains both `fn` (first) and `deps` (rest).
 * @param  {Array|String}   deps... String or array of names of dependencies.
 * @return {Object}         An object contains dependencies injected function and deps.
 */
Pipeline.prototype.buildPipe = function(fn, deps, supplies) {
  if ('function' !== typeof fn) {
    throw new Error('fn should be a function, got ' + typeof fn)
  }

  if ('string' === typeof deps) {
    deps = [deps]
  }

  if ('string' === typeof supplies) {
    supplies = [supplies]
  }

  if (deps && !isArray(deps)) {
    throw new Error('deps should be either string or array of dependency names')
  }

  if (supplies && !isArray(supplies)) {
    throw new Error('deps should be either string or array of dependency names')
  }

  if (isArray(deps)) {
    var firstDep = deps[0]
    if ('number' === typeof firstDep && firstDep > 0) {
      deps[0] = null
      while (firstDep > 1) {
        deps.unshift(null)
        firstDep--
      }
    }
  }

  // get our injected version of pipe function
  var injectedFn = this.injector.inject(fn, deps)

  // return injectable function with depedencies array
  return {
    fn: injectedFn,
    ofn: fn,
    deps: deps,
    supplies: supplies
  }
}

Pipeline.prototype.slice = function(begin, end) {
  var sliced = new Pipeline(this.superpipe)
  sliced.pipes = slice(this.pipes, begin, end)

  return sliced
}

Pipeline.prototype.concat = function(pipeline, begin, end) {
  var concated = this.slice()
  concated.pipes = concated.pipes.concat(slice(pipeline.pipes, begin, end))

  return concated
}

Pipeline.prototype.toPipe = function(begin, end) {
  return Pipeline.toPipe(this, slice(this.pipes, begin, end), this.superpipe)
}

/**
 * Set the error handler for this pipeline.
 *
 * @param  {Function|String} fn
 *         - Function: The pipe function
 *         - String: Name of function which could be found in dependencies.
 * @param  {Array|String}   deps... String or array of names of dependencies.
 * @return {Pipeline}       Instance of this Pipeline.
 */
Pipeline.prototype.error = function(fn, deps) {
  if ('number' === typeof fn) {
    throw new Error('Error handler does not accept numeric argument.')
  }
  this.errorHandler = this.pipe(fn, deps).pipes.pop()
  return this
}

Pipeline.toPipe = function(pipeline, pipes, superpipe) {
  return function() {
    if (pipes[0]) {
      var step = 0
      var args = toArray(arguments)
      var injector = new Injector(pipeline.injector)

      var setDep = bind(superpipe.setDep, {
        injector: injector
      })

      // global deps setter for sharing states cross pipelines
      injector.set('set', function() {
        superpipe.setDep.apply(superpipe, arguments)
        setDep.apply(null, arguments)
      })

      // local deps setter unique for each pipeline
      injector.set('setDep', setDep)

      var next = function(err) {
        var pipe
        if (err) {
          if (pipeline.errorHandler) {
            // error handler fn as a pipe
            pipe = pipeline.errorHandler
            // assign the err object to the first argument
            // so the error handler can get the error later
            args[0] = err
          } else {
            console.warn('No error handler function')
            console.error(err)
          }
        } else {
          // get the right unit from the pipes array
          pipe = pipes[step++]
        }

        if (pipe) {
          var fn = pipe.fn
          var deps = pipe.deps
          var fnName = pipe.fnName
          var result
          var resultReturned = false
          var supplies = pipe.supplies
          // No deps or has deps but next is not required as dependency.
          var autoNext = !deps || (indexOf(deps, 'next') === -1)

          // Get the pipe function demanded from dependency container.
          if ('string' === typeof fnName) {
            // Set it as the original pipe function
            pipe.ofn = injector.get(fnName)
          }

          if (pipe.optional) {
            // Optional pipe, go next if we can't get all dependencies.
            var _deps = injector.getAll(deps)
            if (_deps.indexOf(undefined) > -1) {
              next()
              return
            }
          }

          var modifiedSetDep
          var modifySetDep = supplies && supplies.length > 0
          if (modifySetDep) {
            // Modify the `setDep`
            var fulfilled = autoNext && []
            // We will handle the auto next behaviour.
            autoNext = false

            var inSupplies
            injector.set('setDep', function(name, dep, props) {
              var normalized = superpipe.normalizeSetDepArgs(name, dep, props)
              var _props = normalized.props || (normalized.name ? [normalized.name] : [])

              // Whether dependency being injected is defined in `supplies`.
              inSupplies = every(_props, function(propName) {
                if (-1 === indexOf(supplies, propName)) {
                  next('Dependency "' + propName + '" is not defined in supplies.')
                  return false
                }
                if (fulfilled && -1 === indexOf(fulfilled, propName)) {
                  fulfilled.push(propName)
                }
                return true
              })

              if (!inSupplies) {
                return
              }

              // Set the dependencies.
              setDep(name, dep, props)

              if (fulfilled && fulfilled.length === supplies.length) {
                // Set the auto next to true or call next when all required
                // supplies are fulfilled.
                if ('undefined' === typeof result) {
                  // Result is undefined:
                  if (resultReturned) {
                    // The undefined value has been returned by function.
                    // Call next.
                    next()
                  } else {
                    // Function has not been returned, set autoNext to true so
                    // the code below can handle the auto next.
                    autoNext = true
                  }
                } else if (false !== result) {
                  // Function has been returned (async) and it's not false.
                  // Go next.
                  next()
                }
              } else if (fulfilled && fulfilled.length > supplies.length) {
                next('Get more dependencies than what supplies required.')
              }
            })
          }

          // Run the actual function
          result = fn.apply(injector, args)
          resultReturned = true

          if (modifySetDep) {
            // Put back the original `setDep` function.
            injector.set('setDep', setDep)
          }

          // check if we need to run next automatically
          if (autoNext && !err && result !== false) {
            // call next pipe when the returned value is not false
            next()
          }
        }
      }
      // register `next` function as dependency
      injector.set('next', next)
      // start executing the chain
      next()
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

var path = require('path'),
    url = require('url'),
    util = require('util');

var Q = require('q');

var lib = require('manifoldjs-lib');

var CustomError = lib.CustomError,
    fileTools = lib.fileTools,
    manifestTools = lib.manifestTools,
    PlatformBase = lib.PlatformBase,
    processTools = lib.processTools,
    projectTools = lib.projectTools,
    exec = lib.processTools.exec,
    utils = lib.utils;

var constants = require('./constants');

function Platform (packageName, platforms) {

  var self = this;

  PlatformBase.call(this, constants.platform.id, constants.platform.name, packageName, __dirname);

  // save platform list
  self.platforms = platforms;

  // returns the path to the cordova shell command
  var cachedCordovaPath;
  function getCordovaPath () {

    if (!cachedCordovaPath) {
      // npm command in Windows is a batch file and needs to include extension to be resolved by spawn call
      var cordova = (process.platform === 'win32' ? 'cordova.cmd' : 'cordova');
      return processTools.getCommandPath(__dirname, cordova)
      .then(function (commandPath) {
        cachedCordovaPath = commandPath;
        if (!commandPath) {
          return Q.reject(new Error('Failed to locate the Cordova shell command: \'' + cordova + '\'.'));
        }

        return cachedCordovaPath = commandPath;
      });
    }

    return Q.resolve(cachedCordovaPath);
  }

  // ID or URL of the Hosted Web App plugin - THIS SETTING WILL NEED TO BE UPDATED IF THE PLUGIN IS RELOCATED
  // TODO: make this overridable via environment variable
  var pluginIdOrUrl = 'cordova-plugin-hostedwebapp@>=0.2.0 <0.3.0';

  function createApp (rootDir, appName, packageName, cordovaAppName, callback) {
    self.info('Creating the ' + constants.platform.name + ' project...');
    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['create', appName, packageName, cordovaAppName], { cwd: rootDir, statusMessage: 'Creating app ' });
    })
    .catch(function (err) {
      return Q.reject(new CustomError('Failed to create the base Cordova application.', err));
    })
    .nodeify(callback);
  }

  function addPlatforms (rootDir, platforms, callback) {
    var allPlatforms = platforms.join(' ');
    self.info('Adding the following Cordova platforms: ' + allPlatforms + '...');
    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['platform', 'add'].concat(platforms), { cwd: rootDir, statusMessage: 'Adding platforms ' });
    })
    .catch(function (err) {
      return Q.reject(new CustomError('Failed to add the Cordova platforms: ' + allPlatforms + '.', err));
    })
    .nodeify(callback);
  }

  function addPlugins (rootDir, options, callback) {
    var pluginList = [pluginIdOrUrl];
    if (options.crosswalk) {
      pluginList.push('cordova-plugin-crosswalk-webview');
    }

    if (options.webAppToolkit) {
      pluginList.push('cordova-plugin-webapptoolkit');
      self.warn('\n*******************************************************************************');
      self.warn('The WAT plugin requires you to perform manual steps before running the app');
      self.warn('Follow the steps described here: https://github.com/manifoldjs/Web-App-ToolKit');
      self.warn('*******************************************************************************\n');
    }

    // Fixes an issue in Cordova that requires a version of cordova-ios that is not released yet
    // and stops automated plugin installations - see https://issues.apache.org/jira/browse/CB-9232
    // and https://issues.apache.org/jira/browse/CB-916) - Needs to be removed once a fix is released!!!!
    pluginList.push('cordova-plugin-whitelist@1.0.0');

    var allPlugins = pluginList.join(' ');
    self.info('Adding the following plugins to the Cordova project: ' + allPlugins + '...');

    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['plugin', 'add'].concat(pluginList), { cwd: rootDir, statusMessage: 'Adding plugins ' });
    })
    .catch(function (err) {
      return Q.reject(new CustomError('Failed to add one or more plugins.', err));
    })
    .nodeify(callback);
  }

  function processPlatforms (w3cManifestInfo, rootDir, platformDir, platforms) {

    return Q.allSettled(platforms.map(function (platform) {
      self.info('Processing the \'' + platform + '\' Cordova platform...');

      var cordovaPlatformPath = path.join(platformDir, 'platforms', platform);

      // copy the documentation file
      return self.copyDocumentation(cordovaPlatformPath, platform)
        // create top-level platform shortcut
        .then(function () {
          // don't create a shortcut for the Windows platform
          if (platform.toUpperCase() !== 'WINDOWS') {
            self.info('Creating a shortcut for the \'' + platform + '\' Cordova platform...');
            var srcpath = path.resolve(platformDir, 'platforms', platform);
            var dstpath = path.resolve(rootDir, platform);
            return fileTools.createShortcut(srcpath, dstpath);
          }
        })
        // write generation info (telemetry)
        .then(function () {
          return self.writeGenerationInfo(w3cManifestInfo, cordovaPlatformPath);
        });
    }));
  }

  // override create function
  self.create = function (w3cManifestInfo, rootDir, options, callback) {

    var allPlatforms = self.platforms.map(function (platformId) {
      return constants.platform.subPlatforms[platformId].name;
    }).join(', ');

    self.info('Generating the ' + allPlatforms + ' app(s)...');

    var platformDir = path.join(rootDir, constants.platform.id);

    // generate a reverse-domain-style package name from the manifest's start_url
    var packageName = '';
    url.parse(w3cManifestInfo.content.start_url)
              .hostname
              .replace(/-/g, '')
              .split('.')
              .map(function (segment) {
                // BUG:  Issue 149 aparently "in" is a reserved word for android package names
                if(segment === 'in') {
                  segment = segment.replace('in', 'ind');
                }

                packageName = segment + (packageName ? '.' : '') + packageName;
              });

    var cordovaAppName = utils.sanitizeName(w3cManifestInfo.content.short_name);
    packageName = utils.sanitizeName(packageName);

    // package name must look like: com.company.name - required when start_url is http://localhost, for example
    if (packageName.indexOf('.') < 0) {
      packageName += '.manifoldjs';
    }

    // create the base Cordova app
    return createApp(rootDir, constants.platform.id, packageName, cordovaAppName)
      // persist the manifest
      .then(function () {
        self.info('Copying the ' + constants.platform.name + ' manifest to the app folder...');
        var manifestFilePath = path.join(platformDir, 'manifest.json');
        return manifestTools.writeToFile(w3cManifestInfo, manifestFilePath);
      })
      // add the plugins
      .then (function () {
        return addPlugins(platformDir, options);
      })
      // add the platforms
      .then (function () {
        return addPlatforms(platformDir, self.platforms);
      })
      // process individual platforms
      .then (function () {
        return processPlatforms(w3cManifestInfo, rootDir, platformDir, self.platforms);
      })
      // write generation info (telemetry)
      .then(function () {
        return self.writeGenerationInfo(w3cManifestInfo, platformDir);
      })
      .nodeify(callback);
  };

  // override package function
  self.package = function (projectDir, options, callback) {

    var allPlatforms = self.platforms.map(function (platformId) {
      return constants.platform.subPlatforms[platformId].name;
    }).join(', ');

    self.info('Creating app packages for the following Cordova platforms: ' + allPlatforms + '...');

    var validPlatforms = self.platforms.filter(function (platform) {
      if (platform === constants.platform.subPlatforms.ios.id && process.platform !== 'darwin') {
        self.warn('Packaging apps for the \'' + constants.platform.subPlatforms.ios.name + '\' is not supported in this environment.');
        return false;
      }

      return true;
    });

    var platformDir = path.join(projectDir || process.cwd(), constants.platform.id);
    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['build'].concat(validPlatforms), { cwd: platformDir });
    })
    .nodeify(callback);
  };

  self.run = function (projectDir, options, callback) {
    var platform = self.platforms[0];

    if (platform === constants.platform.subPlatforms.windows && !utils.isWindows) {
      return Q.reject(new Error('Windows projects can only be executed in Windows environments.')).nodeify(callback);
    }

    self.info('Running app for the ' + platform + ' platform...');

    var platformDir = path.join(projectDir || process.cwd(), 'cordova');

    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['run', platform], { cwd: platformDir, statusMessage: 'Launching app ' });
    })
    .nodeify(callback);
  };

  self.open = function (projectDir, options, callback) {
    var platform = self.platforms[0];
    if (platform !== constants.platform.subPlatforms.windows.id) {
      return Q.reject(new Error('The \'open\' command is not implemented for the \'' + platform + '\' platform.')).nodeify(callback);
    }

    if (platform === constants.platform.subPlatforms.windows.id && process.platform !== 'win32') {
      return Q.reject(new Error('Visual Studio projects can only be opened in Windows environments.')).nodeify(callback);
    }

    var platformDir = path.join(projectDir || process.cwd(), constants.platform.id);
    var projectFilename = path.join(platformDir, 'platforms', platform, 'CordovaApp.sln');
    return projectTools.openVisualStudioProject(projectFilename).nodeify(callback);
  };
}

util.inherits(Platform, PlatformBase);

module.exports = Platform;
<commit_msg>Support overriding Hosted Web App plugin location and/or version with an environment variable (CORDOVA_PLUGIN_HOSTEDWEBAPP)<commit_after>'use strict';

var path = require('path'),
    url = require('url'),
    util = require('util');

var Q = require('q');

var lib = require('manifoldjs-lib');

var CustomError = lib.CustomError,
    fileTools = lib.fileTools,
    manifestTools = lib.manifestTools,
    PlatformBase = lib.PlatformBase,
    processTools = lib.processTools,
    projectTools = lib.projectTools,
    exec = lib.processTools.exec,
    utils = lib.utils;

var constants = require('./constants');

// ID or URL of the Hosted Web App plugin
// Specifies the location and version constraints (if applicable) of the plugin.
// Can be overriden by defining a CORDOVA_PLUGIN_HOSTEDWEBAPP environment variable. For example:
//   SET "CORDOVA_PLUGIN_HOSTEDWEBAPP=cordova-plugin-hostedwebapp@0.4.5"
var pluginIdOrUrl = process.env.CORDOVA_PLUGIN_HOSTEDWEBAPP || 'cordova-plugin-hostedwebapp@>=0.3.0 <0.4.0';

function Platform (packageName, platforms) {

  var self = this;

  PlatformBase.call(this, constants.platform.id, constants.platform.name, packageName, __dirname);

  // save platform list
  self.platforms = platforms;

  // returns the path to the cordova shell command
  var cachedCordovaPath;
  function getCordovaPath () {

    if (!cachedCordovaPath) {
      // npm command in Windows is a batch file and needs to include extension to be resolved by spawn call
      var cordova = (process.platform === 'win32' ? 'cordova.cmd' : 'cordova');
      return processTools.getCommandPath(__dirname, cordova)
      .then(function (commandPath) {
        cachedCordovaPath = commandPath;
        if (!commandPath) {
          return Q.reject(new Error('Failed to locate the Cordova shell command: \'' + cordova + '\'.'));
        }

        return cachedCordovaPath = commandPath;
      });
    }

    return Q.resolve(cachedCordovaPath);
  }

  function createApp (rootDir, appName, packageName, cordovaAppName, callback) {
    self.info('Creating the ' + constants.platform.name + ' project...');
    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['create', appName, packageName, cordovaAppName], { cwd: rootDir, statusMessage: 'Creating app ' });
    })
    .catch(function (err) {
      return Q.reject(new CustomError('Failed to create the base Cordova application.', err));
    })
    .nodeify(callback);
  }

  function addPlatforms (rootDir, platforms, callback) {
    var allPlatforms = platforms.join(' ');
    self.info('Adding the following Cordova platforms: ' + allPlatforms + '...');
    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['platform', 'add'].concat(platforms), { cwd: rootDir, statusMessage: 'Adding platforms ' });
    })
    .catch(function (err) {
      return Q.reject(new CustomError('Failed to add the Cordova platforms: ' + allPlatforms + '.', err));
    })
    .nodeify(callback);
  }

  function addPlugins (rootDir, options, callback) {
    var pluginList = [pluginIdOrUrl];
    if (options.crosswalk) {
      pluginList.push('cordova-plugin-crosswalk-webview');
    }

    if (options.webAppToolkit) {
      pluginList.push('cordova-plugin-webapptoolkit');
      self.warn('\n*******************************************************************************');
      self.warn('The WAT plugin requires you to perform manual steps before running the app');
      self.warn('Follow the steps described here: https://github.com/manifoldjs/Web-App-ToolKit');
      self.warn('*******************************************************************************\n');
    }

    // Fixes an issue in Cordova that requires a version of cordova-ios that is not released yet
    // and stops automated plugin installations - see https://issues.apache.org/jira/browse/CB-9232
    // and https://issues.apache.org/jira/browse/CB-916) - Needs to be removed once a fix is released!!!!
    pluginList.push('cordova-plugin-whitelist@1.0.0');

    var allPlugins = pluginList.join(' ');
    self.info('Adding the following plugins to the Cordova project: ' + allPlugins + '...');

    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['plugin', 'add'].concat(pluginList), { cwd: rootDir, statusMessage: 'Adding plugins ' });
    })
    .catch(function (err) {
      return Q.reject(new CustomError('Failed to add one or more plugins.', err));
    })
    .nodeify(callback);
  }

  function processPlatforms (w3cManifestInfo, rootDir, platformDir, platforms) {

    return Q.allSettled(platforms.map(function (platform) {
      self.info('Processing the \'' + platform + '\' Cordova platform...');

      var cordovaPlatformPath = path.join(platformDir, 'platforms', platform);

      // copy the documentation file
      return self.copyDocumentation(cordovaPlatformPath, platform)
        // create top-level platform shortcut
        .then(function () {
          // don't create a shortcut for the Windows platform
          if (platform.toUpperCase() !== 'WINDOWS') {
            self.info('Creating a shortcut for the \'' + platform + '\' Cordova platform...');
            var srcpath = path.resolve(platformDir, 'platforms', platform);
            var dstpath = path.resolve(rootDir, platform);
            return fileTools.createShortcut(srcpath, dstpath);
          }
        })
        // write generation info (telemetry)
        .then(function () {
          return self.writeGenerationInfo(w3cManifestInfo, cordovaPlatformPath);
        });
    }));
  }

  // override create function
  self.create = function (w3cManifestInfo, rootDir, options, callback) {

    var allPlatforms = self.platforms.map(function (platformId) {
      return constants.platform.subPlatforms[platformId].name;
    }).join(', ');

    self.info('Generating the ' + allPlatforms + ' app(s)...');

    var platformDir = path.join(rootDir, constants.platform.id);

    // generate a reverse-domain-style package name from the manifest's start_url
    var packageName = '';
    url.parse(w3cManifestInfo.content.start_url)
              .hostname
              .replace(/-/g, '')
              .split('.')
              .map(function (segment) {
                // BUG:  Issue 149 aparently "in" is a reserved word for android package names
                if(segment === 'in') {
                  segment = segment.replace('in', 'ind');
                }

                packageName = segment + (packageName ? '.' : '') + packageName;
              });

    var cordovaAppName = utils.sanitizeName(w3cManifestInfo.content.short_name);
    packageName = utils.sanitizeName(packageName);

    // package name must look like: com.company.name - required when start_url is http://localhost, for example
    if (packageName.indexOf('.') < 0) {
      packageName += '.manifoldjs';
    }

    // create the base Cordova app
    return createApp(rootDir, constants.platform.id, packageName, cordovaAppName)
      // persist the manifest
      .then(function () {
        self.info('Copying the ' + constants.platform.name + ' manifest to the app folder...');
        var manifestFilePath = path.join(platformDir, 'manifest.json');
        return manifestTools.writeToFile(w3cManifestInfo, manifestFilePath);
      })
      // add the plugins
      .then (function () {
        return addPlugins(platformDir, options);
      })
      // add the platforms
      .then (function () {
        return addPlatforms(platformDir, self.platforms);
      })
      // process individual platforms
      .then (function () {
        return processPlatforms(w3cManifestInfo, rootDir, platformDir, self.platforms);
      })
      // write generation info (telemetry)
      .then(function () {
        return self.writeGenerationInfo(w3cManifestInfo, platformDir);
      })
      .nodeify(callback);
  };

  // override package function
  self.package = function (projectDir, options, callback) {

    var allPlatforms = self.platforms.map(function (platformId) {
      return constants.platform.subPlatforms[platformId].name;
    }).join(', ');

    self.info('Creating app packages for the following Cordova platforms: ' + allPlatforms + '...');

    var validPlatforms = self.platforms.filter(function (platform) {
      if (platform === constants.platform.subPlatforms.ios.id && process.platform !== 'darwin') {
        self.warn('Packaging apps for the \'' + constants.platform.subPlatforms.ios.name + '\' is not supported in this environment.');
        return false;
      }

      return true;
    });

    var platformDir = path.join(projectDir || process.cwd(), constants.platform.id);
    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['build'].concat(validPlatforms), { cwd: platformDir });
    })
    .nodeify(callback);
  };

  self.run = function (projectDir, options, callback) {
    var platform = self.platforms[0];

    if (platform === constants.platform.subPlatforms.windows && !utils.isWindows) {
      return Q.reject(new Error('Windows projects can only be executed in Windows environments.')).nodeify(callback);
    }

    self.info('Running app for the ' + platform + ' platform...');

    var platformDir = path.join(projectDir || process.cwd(), 'cordova');

    return getCordovaPath().then(function (cordovaPath) {
      return exec(cordovaPath, ['run', platform], { cwd: platformDir, statusMessage: 'Launching app ' });
    })
    .nodeify(callback);
  };

  self.open = function (projectDir, options, callback) {
    var platform = self.platforms[0];
    if (platform !== constants.platform.subPlatforms.windows.id) {
      return Q.reject(new Error('The \'open\' command is not implemented for the \'' + platform + '\' platform.')).nodeify(callback);
    }

    if (platform === constants.platform.subPlatforms.windows.id && process.platform !== 'win32') {
      return Q.reject(new Error('Visual Studio projects can only be opened in Windows environments.')).nodeify(callback);
    }

    var platformDir = path.join(projectDir || process.cwd(), constants.platform.id);
    var projectFilename = path.join(platformDir, 'platforms', platform, 'CordovaApp.sln');
    return projectTools.openVisualStudioProject(projectFilename).nodeify(callback);
  };
}

util.inherits(Platform, PlatformBase);

module.exports = Platform;
<|endoftext|>


<commit_before>var cable = require('cable');
var events = require('events');
var pump = require('pump');
var util = require('util');

var empty = new Buffer(0);

var parse = function(cb) {
	return cb && function(err, buffer) {
		if (err) return cb(err);
		cb(null, JSON.parse(buffer.toString()));
	};
};

var stringify = function(cb) {
	return function(err, obj) {
		if (err) return cb(err);
		cb(null, JSON.stringify(obj === undefined ? null : obj));
	};
};

var Protocol = function(socket, data) {
	var self = this;

	events.EventEmitter.call(this);

	this._cable = cable();
	this._cable.on('message', function(message, cb) {
		var opcode = message[0];
		var idLen = message.readUInt16LE(1);
		var id = message.toString('utf-8', 3, 3+idLen);
		var payload = message.slice(3+idLen);

		var emit = function() {
			self.emit.apply(self, arguments) || cb(new Error('Command is not supported'));
		};

		switch (opcode) {
			case 0:  return emit('get', id, stringify(cb));
			case 1:  return emit('add', id, JSON.parse(payload.toString()), cb);
			case 2:  return emit('update', id, JSON.parse(payload.toString()), cb);
			case 3:  return emit('remove', id, cb);
			case 4:  return emit('list', stringify(cb));
			case 5:  return emit('ps', stringify(cb));
			case 6:  return emit('start', id, cb);
			case 7:  return emit('stop', id, cb);
			case 8:  return emit('restart', id, cb);
			case 9:  return emit('distribute', id, cb);
			case 10: return emit('subscribe', id, cb);
			case 11: return emit('unsubscribe', id, cb);
			case 12: return emit('stdout', id, payload);
			case 13: return emit('stderr', id, payload);
			case 14: return emit('spawn', id, JSON.parse(payload.toString()));
			case 15: return emit('exit', id, JSON.parse(payload.toString()));
		}

		cb(new Error('Command is not supported'));
	});

	process.nextTick(function() {
		if (data) self._cable.write(data);
		pump(socket, self._cable, socket, function() {
			self.emit('close');
		});
	});
};

util.inherits(Protocol, events.EventEmitter);

Protocol.prototype.get = function(id, cb) {
	this._send(0, id, null, parse(cb));
};

Protocol.prototype.add = function(id, service, cb) {
	this._send(1, id, JSON.stringify(service), cb);
};

Protocol.prototype.update = function(id, service, cb) {
	this._send(2, id, JSON.stringify(service), cb);
};

Protocol.prototype.remove = function(id, cb) {
	this._send(3, id, null, cb);
};

Protocol.prototype.list = function(cb) {
	this._send(4, '', null, parse(cb));
};

Protocol.prototype.ps = function(cb) {
	this._send(5, '', null, parse(cb));
};

Protocol.prototype.start = function(id, cb) {
	this._send(6, id, null, cb);
};

Protocol.prototype.stop = function(id, cb) {
	this._send(7, id, null, cb);
};

Protocol.prototype.restart = function(id, cb) {
	this._send(8, id, null, cb);
};

Protocol.prototype.distribute = function(id, service, cb) {
	this._send(9, id, service, cb);
};

Protocol.prototype.subscribe = function(id, cb) {
	this._send(10, id, null, cb);
};

Protocol.prototype.unsubscribe = function(id, cb) {
	this._send(11, id, null, cb);
};

Protocol.prototype.stdout = function(id, data) {
	this._send(12, id, data);
};

Protocol.prototype.stderr = function(id, data) {
	this._send(13, id, data);
};

Protocol.prototype.spawn = function(id, pid) {
	this._send(14, id, JSON.stringify(pid));
};

Protocol.prototype.exit = function(id, code) {
	this._send(15, id, JSON.stringify(code));
};

Protocol.prototype.destroy = function() {
	this._cable.destroy();
};

Protocol.prototype._send = function(opcode, id, payload, cb) {
	if (!payload) payload = empty;
	if (!Buffer.isBuffer(payload)) payload = new Buffer(payload);

	var idLen = Buffer.byteLength(id);
	var message = new Buffer(3+idLen+payload.length);

	message[0] = opcode;
	message.writeUInt16LE(idLen, 1);
	message.write(id, 3);
	if (payload.length) payload.copy(message, 3+idLen);

	this._cable.send(message, cb);
};

module.exports = function(socket, data) {
	return new Protocol(socket, data);
};<commit_msg>fixed distribute method<commit_after>var cable = require('cable');
var events = require('events');
var pump = require('pump');
var util = require('util');

var empty = new Buffer(0);

var parse = function(cb) {
	return cb && function(err, buffer) {
		if (err) return cb(err);
		cb(null, JSON.parse(buffer.toString()));
	};
};

var stringify = function(cb) {
	return function(err, obj) {
		if (err) return cb(err);
		cb(null, JSON.stringify(obj === undefined ? null : obj));
	};
};

var Protocol = function(socket, data) {
	var self = this;

	events.EventEmitter.call(this);

	this._cable = cable();
	this._cable.on('message', function(message, cb) {
		var opcode = message[0];
		var idLen = message.readUInt16LE(1);
		var id = message.toString('utf-8', 3, 3+idLen);
		var payload = message.slice(3+idLen);

		var emit = function() {
			self.emit.apply(self, arguments) || cb(new Error('Command is not supported'));
		};

		switch (opcode) {
			case 0:  return emit('get', id, stringify(cb));
			case 1:  return emit('add', id, JSON.parse(payload.toString()), cb);
			case 2:  return emit('update', id, JSON.parse(payload.toString()), cb);
			case 3:  return emit('remove', id, cb);
			case 4:  return emit('list', stringify(cb));
			case 5:  return emit('ps', stringify(cb));
			case 6:  return emit('start', id, cb);
			case 7:  return emit('stop', id, cb);
			case 8:  return emit('restart', id, cb);
			case 9:  return emit('distribute', id, JSON.parse(payload.toString()), cb);
			case 10: return emit('subscribe', id, cb);
			case 11: return emit('unsubscribe', id, cb);
			case 12: return emit('stdout', id, payload);
			case 13: return emit('stderr', id, payload);
			case 14: return emit('spawn', id, JSON.parse(payload.toString()));
			case 15: return emit('exit', id, JSON.parse(payload.toString()));
		}

		cb(new Error('Command is not supported'));
	});

	process.nextTick(function() {
		if (data) self._cable.write(data);
		pump(socket, self._cable, socket, function() {
			self.emit('close');
		});
	});
};

util.inherits(Protocol, events.EventEmitter);

Protocol.prototype.get = function(id, cb) {
	this._send(0, id, null, parse(cb));
};

Protocol.prototype.add = function(id, service, cb) {
	this._send(1, id, JSON.stringify(service), cb);
};

Protocol.prototype.update = function(id, service, cb) {
	this._send(2, id, JSON.stringify(service), cb);
};

Protocol.prototype.remove = function(id, cb) {
	this._send(3, id, null, cb);
};

Protocol.prototype.list = function(cb) {
	this._send(4, '', null, parse(cb));
};

Protocol.prototype.ps = function(cb) {
	this._send(5, '', null, parse(cb));
};

Protocol.prototype.start = function(id, cb) {
	this._send(6, id, null, cb);
};

Protocol.prototype.stop = function(id, cb) {
	this._send(7, id, null, cb);
};

Protocol.prototype.restart = function(id, cb) {
	this._send(8, id, null, cb);
};

Protocol.prototype.distribute = function(id, service, cb) {
	this._send(9, id, JSON.stringify(service), cb);
};

Protocol.prototype.subscribe = function(id, cb) {
	this._send(10, id, null, cb);
};

Protocol.prototype.unsubscribe = function(id, cb) {
	this._send(11, id, null, cb);
};

Protocol.prototype.stdout = function(id, data) {
	this._send(12, id, data);
};

Protocol.prototype.stderr = function(id, data) {
	this._send(13, id, data);
};

Protocol.prototype.spawn = function(id, pid) {
	this._send(14, id, JSON.stringify(pid));
};

Protocol.prototype.exit = function(id, code) {
	this._send(15, id, JSON.stringify(code));
};

Protocol.prototype.destroy = function() {
	this._cable.destroy();
};

Protocol.prototype._send = function(opcode, id, payload, cb) {
	if (!payload) payload = empty;
	if (!Buffer.isBuffer(payload)) payload = new Buffer(payload);

	var idLen = Buffer.byteLength(id);
	var message = new Buffer(3+idLen+payload.length);

	message[0] = opcode;
	message.writeUInt16LE(idLen, 1);
	message.write(id, 3);
	if (payload.length) payload.copy(message, 3+idLen);

	this._cable.send(message, cb);
};

module.exports = function(socket, data) {
	return new Protocol(socket, data);
};<|endoftext|>


<commit_before>//META {"name": "BetterReplyer", "source": "https://github.com/Zerthox/BetterDiscord-Plugins/blob/master/plugins/BetterReplyer.plugin.js"} *//

/**
 * BetterReplyer plugin class
 * @author Zerthox
 * @version 3.0.1
 */
class BetterReplyer {

	/**
	 * @return {string} plugin name
	 */
	getName() {
		return "BetterReplyer";
    }

    /**
     * @return {string} plugin description
     */
	getDescription() {
		return "Reply to people using their ID with a button. Inspired by Replyer by @Hammock#3110, @Natsulus#0001 & @Zerebos#7790. Using getInternalInstance by @noodlebox#0155.";
    }

    /**
     * @return {string} plugin version
     */
	getVersion() {
		return "3.0.1";
	}
    /**
     * @return {string} plugin author
     */
	getAuthor() {
		return "Zerthox";
	}

	constructor() {

		/**
		 * object with selectors
		 */
		this.selector = {
			messageHeader: ".messages-3amgkR .headerCozyMeta-rdohGq",
			channelTextarea: ".textArea-2Spzkt",
			accountDetails: ".accountDetails-3k9g4n"
		};

		/**
		 * plugin styles
		 */
		this.css = `/* BetterReplyer CSS */
			.replyer {
				position: relative;
				top: -1px;
				margin-left: 5px;
				padding: 3px 5px;
				background: rgba(0, 0, 0, 0.4);
				border-radius: 3px;
				color: #fff !important;
				font-size: 10px;
				text-transform: uppercase;
				cursor: pointer;
			}
			.container-1YxwTf:not(:hover) .replyer {
				visibility: hidden;
			}`;

		/**
		 * object with handlers
		 */
		this.handler = {
			click: (e) => {
				this.curr = e.target;
			},
			blur: () => {
				if (this.curr.matches(`${this.selector.messageHeader} .replyer`)) {
					this.mode = true;
				}
				else {
					this.mode = false;
				}
			}
		};

		/**
		 * clicked element save
		 */
		this.clicked = document.body;

		/**
		 * reply mode
		 */
		this.mode = false;
	}

	 /**
     * plugin start function
     */
	start() {

		// inject styles
		BdApi.injectCSS(this.getName(), this.css);

		// add click handler
		document.addEventListener("mousedown", this.handler.click);

        // iterate over all message headers
        for (var e of document.querySelectorAll(this.selector.messageHeader)) {

            // insert reply button
            this.insert(e);
		}

		// iterate over all textareas
		for (var e of document.querySelectorAll(this.selector.channelTextarea)) {

			// add blur handler
			e.addEventListener("blur", this.handler.blur);
		}

        // console output
		console.log(`[${this.getName()}] Enabled`);
    }

    /**
     * plugin stop function
     */
	stop() {

		// remove styles
		BdApi.clearCSS(this.getName());

		// remove click handler
		document.removeEventListener("mousedown", this.handler.click);

        // iterate over all message headers
        for (var e of document.querySelectorAll(`${this.selector.messageHeader} .replyer`)) {

            // remove reply button
            e.remove();
		}

		// iterate over all textareas
		for (var e of document.querySelectorAll(this.selector.channelTextarea)) {

			// remove blur handler
			e.removeEventListener("blur", this.handler.blur);
		}

        // console output
		console.log(`[${this.getName()}] Disabled`);
	}

	/**
	 * plugin DOM observer
	 * @param {*} e event
	 */
	observer(e) {

		// iterate over added nodes
        for (var n of e.addedNodes) {

            // check if node is html element
            if (n instanceof HTMLElement) {

				// check if added nodes contain message header or textarea
                if (n.matches(this.selector.messageHeader)) {

					// insert directly into message header
                    this.insert(n);
                }
				else if (n.matches(this.selector.channelTextarea)) {

					// add blur handler directly to textarea
					n.addEventListener("blur", this.handler.blur);
				}
                else {

					// search for descendant message headers
					var l = n.findAll(this.selector.messageHeader);
					if (l.length > 0) {

						// insert into descendant message headers
						for (var m of l) {
							this.insert(m);
						}
					}

					// search for descendant textareas
					l = n.findAll(this.selector.channelTextarea);
					if (l.length > 0) {

						// add blur handler to descendant textareas
						for (var t of l) {
							t.addEventListener("blur", this.handler.blur);
						}
					}
				}
            }
        }
	}

	/**
	 * insert reply button into passed message header
	 * @param {HTMLElement} e message header
	 */
	insert(e) {

		// get message author id
		var id = this.getInternalInstance(e).child.memoizedProps.message.author.id;

		// check if message is sent by user
		if (id != this.getInternalInstance(document.querySelector(this.selector.accountDetails)).return.memoizedProps.user.id) {

			// create reply button element
			var b = document.createElement("span");

			// set reply button class
			b.className = "replyer";

			// set reply button text
			b.innerHTML = "Reply";

			// add event listener
			b.addEventListener("click", () => {

				// iterate over all channel textareas
				for (var t of document.querySelectorAll(this.selector.channelTextarea)) {

					// get mention string
					var m = `<@!${id}>`;

					// focus textarea
					t.focus();

					// check which mode to use
					if (this.mode) {

						// insert directly
						document.execCommand("insertText", false, m);
					}
					else {

						// add space to mention string
						m += " ";

						// save selection
						var ts = t.selectionStart + m.length,
							te = t.selectionEnd + m.length;

						// go to start of textarea
						t.selectionEnd = 0;

						// insert mention
						document.execCommand("insertText", false, m);

						// select saved selection
						t.setSelectionRange(ts, te);
					}
					}
			});

			// append reply button
			e.appendChild(b);
		}
	}

	/**
     * getInternalInstance by @noodlebox
	 * @author noodlebox
	 * @param {HTMLElement} e element
	 * @return {*} react internal instance of passed element
     */
    getInternalInstance(e) {
        return e[Object.keys(e).find(k => k.startsWith("__reactInternalInstance"))];
	}

}<commit_msg>Improve handling multiple textareas<commit_after>//META {"name": "BetterReplyer", "source": "https://github.com/Zerthox/BetterDiscord-Plugins/blob/master/plugins/BetterReplyer.plugin.js"} *//

/**
 * BetterReplyer plugin class
 * @author Zerthox
 * @version 3.0.2
 */
class BetterReplyer {

	/**
	 * @return {string} plugin name
	 */
	getName() {
		return "BetterReplyer";
    }

    /**
     * @return {string} plugin description
     */
	getDescription() {
		return "Reply to people using their ID with a button. Inspired by Replyer by @Hammock#3110, @Natsulus#0001 & @Zerebos#7790. Using getInternalInstance by @noodlebox#0155.";
    }

    /**
     * @return {string} plugin version
     */
	getVersion() {
		return "3.0.2";
	}
    /**
     * @return {string} plugin author
     */
	getAuthor() {
		return "Zerthox";
	}

	constructor() {

		/**
		 * object with selectors
		 */
		this.selector = {
			messageHeader: ".messages-3amgkR .headerCozyMeta-rdohGq",
			channelTextarea: ".textArea-2Spzkt",
			accountDetails: ".accountDetails-3k9g4n"
		};

		/**
		 * plugin styles
		 */
		this.css = `/* BetterReplyer CSS */
			.replyer {
				position: relative;
				top: -1px;
				margin-left: 5px;
				padding: 3px 5px;
				background: rgba(0, 0, 0, 0.4);
				border-radius: 3px;
				color: #fff !important;
				font-size: 10px;
				text-transform: uppercase;
				cursor: pointer;
			}
			.container-1YxwTf:not(:hover) .replyer {
				visibility: hidden;
			}`;

		/**
		 * object with plugin state relevant stuff
		 */
		this.state = {
			/**
			 * clicked element save
			 */
			clicked: document.body,

			/**
			 * focused textarea save
			 */
			focused: null,

			/**
			 * reply mode
			 */
			mode: false
		}

				/**
		 * object with handlers
		 */
		this.handler = {

			/**
			 * document click handler
			 */
			click: (e) => {

				// save event target
				this.state.clicked = e.target;
			},

			/**
			 * textarea blur handler
			 */
			blur: (e) => {

				// save event target
				this.state.focused = e.target;

				// update reply mode
				this.state.mode = this.state.clicked.matches(`${this.selector.messageHeader} .replyer`) ? true : false;
			}
		};
	}

	 /**
     * plugin start function
     */
	start() {

		// inject styles
		BdApi.injectCSS(this.getName(), this.css);

		// add click handler
		document.addEventListener("mousedown", this.handler.click);

        // iterate over all message headers
        for (var e of document.querySelectorAll(this.selector.messageHeader)) {

            // insert reply button
            this.insert(e);
		}

		// iterate over all textareas
		for (var e of document.querySelectorAll(this.selector.channelTextarea)) {

			// add blur handler
			e.addEventListener("blur", this.handler.blur);
		}

        // console output
		console.log(`[${this.getName()}] Enabled`);
    }

    /**
     * plugin stop function
     */
	stop() {

		// remove styles
		BdApi.clearCSS(this.getName());

		// remove click handler
		document.removeEventListener("mousedown", this.handler.click);

        // iterate over all message headers
        for (var e of document.querySelectorAll(`${this.selector.messageHeader} .replyer`)) {

            // remove reply button
            e.remove();
		}

		// iterate over all textareas
		for (var e of document.querySelectorAll(this.selector.channelTextarea)) {

			// remove blur handler
			e.removeEventListener("blur", this.handler.blur);
		}

		// reset state
		this.state = {
			clicked: document.body,
			focused: null,
			mode: false
		}

        // console output
		console.log(`[${this.getName()}] Disabled`);
	}

	/**
	 * plugin DOM observer
	 * @param {*} e event
	 */
	observer(e) {

		// iterate over added nodes
        for (var n of e.addedNodes) {

            // check if node is html element
            if (n instanceof HTMLElement) {

				// check if added nodes contain message header or textarea
                if (n.matches(this.selector.messageHeader)) {

					// insert directly into message header
                    this.insert(n);
                }
				else if (n.matches(this.selector.channelTextarea)) {

					// add blur handler directly to textarea
					n.addEventListener("blur", this.handler.blur);
				}
                else {

					// search for descendant message headers
					var l = n.findAll(this.selector.messageHeader);
					if (l.length > 0) {

						// insert into descendant message headers
						for (var m of l) {
							this.insert(m);
						}
					}

					// search for descendant textareas
					l = n.findAll(this.selector.channelTextarea);
					if (l.length > 0) {

						// add blur handler to descendant textareas
						for (var t of l) {
							t.addEventListener("blur", this.handler.blur);
						}
					}
				}
            }
        }
	}

	/**
	 * insert reply button into passed message header
	 * @param {HTMLElement} e message header
	 */
	insert(e) {

		// get message author id
		var id = this.getInternalInstance(e).child.memoizedProps.message.author.id;

		// check if message is sent by user
		if (id != this.getInternalInstance(document.querySelector(this.selector.accountDetails)).return.memoizedProps.user.id) {

			// create reply button element
			var b = document.createElement("span");

			// set reply button class
			b.className = "replyer";

			// set reply button text
			b.innerHTML = "Reply";

			// add event listener
			b.addEventListener("click", () => {

				// get last focused textarea or default to first textarea found
				var t = this.state.focused instanceof HTMLElement ? this.state.focused : document.querySelector(this.selector.channelTextarea);

				// get mention string
				var m = `<@!${id}>`;

				// focus textarea
				t.focus();

				// check which mode to use
				if (this.state.mode) {

					// insert directly
					document.execCommand("insertText", false, m);
				}
				else {

					// add space to mention string
					m += " ";

					// save selection
					var ts = t.selectionStart + m.length,
						te = t.selectionEnd + m.length;

					// go to start of textarea
					t.selectionEnd = 0;

					// insert mention
					document.execCommand("insertText", false, m);

					// select saved selection
					t.setSelectionRange(ts, te);
				}
			});

			// append reply button
			e.appendChild(b);
		}
	}

	/**
     * getInternalInstance by @noodlebox
	 * @author noodlebox
	 * @param {HTMLElement} e element
	 * @return {*} react internal instance of passed element
     */
    getInternalInstance(e) {
        return e[Object.keys(e).find(k => k.startsWith("__reactInternalInstance"))];
	}

}<|endoftext|>


<commit_before>function clone(obj){
	return JSON.parse(JSON.stringify(obj));
}

function dispatch(data){
	data.sender = PLAYER_ID;
	data.timestamp = firebase.database.ServerValue.TIMESTAMP;
	missionRef.push(data);
}

function getPlayerIndex(state, pid){
	return state.players.indexOf(pid);
}

// Keep an eye on this function
function getPlayerIndexByInterface(state, itf){
	var res = false;
	for(var i = 0; i < state.panels.length; i++){
		if(itf in state.panels[i]){
			res = i;
			break;
		}
	}
	console.warn('getPlayerIndexByInterface: ', itf, ' -> ', res);
	return res;
}

function lastItem(list){
	if(list.length > 0){
		return list[list.length-1]
	}
	else{
		return false;
	}
}

function randomItem(list){
	if(list.length > 0){
		var idx = Math.floor(list.length * Math.random());
		return list[idx];
	}
	else{
		return false;
	}
}

function chooseTask(itf){
	var tasks = [];
	for(var t in TASKS){
		if(TASKS[t].interface === itf){
			tasks.push(t);
		}
	}
	var tid = false;
	if(tasks.length > 0){
		var tidx = Math.floor(tasks.length * Math.random());
		tid = tasks[tidx];
	}
	return tid;
}

var REDUCERS = {
	// Inputs: pid
	add_player: (state, data) => {
		var player = data.pid;
		if(!state.playing){
			if(state.players){
				//if(state.players.length === 4){}
				state.players.push(player);
			}
			else{
				state.players = [player];
			}
		}
		else{
			console.error('Cannot join: mission already in progress.');
		}
		return state;
	},
	// Inputs: void
	setup_controls: (state, data) => {
		state.score = {
			completed: [],
			failed: []
		}
		state.players = state.players.sort();
		state.panels = state.players.map((pid, idx) => {
			var panelMap = {}
			var pitfs = PLAYER_ITFS[idx];
			for(var i = 0; i < pitfs.length; i++){
				panelMap[pitfs[i]] = {
					id: pitfs[i],
					dom: 'panel-' + (i + 1),
					active: false,
					tasks: []
				}
			}
			return panelMap;
		});
		state.instructions = state.players.map((pid, idx) => {
			return false;
		});
		return state;
	},
	// Inputs: win, lose, limit
	global_settings: (state, data) => {

		return state;
	},
	// Inputs: void
	start_mission: (state, data) => {
		state.checkedIn = state.checkedIn + 1 || 1;
		if(state.checkedIn === state.players.length){
			state.playing = true;
		}
		return state;
	},
	// Inputs: pid, itf
	start_task: (state, data) => {
		var pidx = getPlayerIndex(state, data.pid);
		var panelMap = state.panels[pidx];
		var tid = chooseTask(data.itf); //Choose tid for the given itf
		var itf = panelMap[data.itf];
		if(itf){
			if(itf.active){
				// Need to handle override case and remove the old instruction from the partner player!
				// Ideally no penalty for this case
			}
			itf.active = true;
			itf.tasks.push(tid);
		}
		else{
			console.error('start_task: could not find interface: ', data.itf);
		}
		return state;
	},
	// Inputs: pid, tid
	start_instruction: (state, data) => {
		var pidx = getPlayerIndex(state, data.pid);
		state.instructions[pidx] = data.tid;
		return state;
	},
	// Inputs: itf, tid
	complete_task: (state, data) => {
		var pidx = getPlayerIndexByInterface(state, data.itf);
		var itf = state.panels[pidx][data.itf];
		if(itf){
			itf.active = false;
		}
		state.score.completed.push(data.tid);
		return state;
	},
	// Inputs: tid
	fail_task: (state, data) => {
		state.score.failed.push(data.tid);
		return state;
	},
	// Inputs: void
	finish_mission: (state, data) => {
		state.playing = false;
		for(var p = 0; p < state.panels.length; p++){
			for(var i in state.panels[p]){
				state.panels[p][i].active = false;
			}
		}
		for(var t = 0; t < state.instructions.length; t++){
			state.instructions[t] = false;
		}
		return state;
	},
	// Inputs: void
	retry: (state, data) => {
		state = {};
		return state;
	}
}<commit_msg>Remove warning from getplayer indexbyinterface<commit_after>function clone(obj){
	return JSON.parse(JSON.stringify(obj));
}

function dispatch(data){
	data.sender = PLAYER_ID;
	data.timestamp = firebase.database.ServerValue.TIMESTAMP;
	missionRef.push(data);
}

function getPlayerIndex(state, pid){
	return state.players.indexOf(pid);
}

// Keep an eye on this function
function getPlayerIndexByInterface(state, itf){
	var res = false;
	for(var i = 0; i < state.panels.length; i++){
		if(itf in state.panels[i]){
			res = i;
			break;
		}
	}
	//console.warn('getPlayerIndexByInterface: ', itf, ' -> ', res);
	return res;
}

function lastItem(list){
	if(list.length > 0){
		return list[list.length-1]
	}
	else{
		return false;
	}
}

function randomItem(list){
	if(list.length > 0){
		var idx = Math.floor(list.length * Math.random());
		return list[idx];
	}
	else{
		return false;
	}
}

function chooseTask(itf){
	var tasks = [];
	for(var t in TASKS){
		if(TASKS[t].interface === itf){
			tasks.push(t);
		}
	}
	var tid = false;
	if(tasks.length > 0){
		var tidx = Math.floor(tasks.length * Math.random());
		tid = tasks[tidx];
	}
	return tid;
}

var REDUCERS = {
	// Inputs: pid
	add_player: (state, data) => {
		var player = data.pid;
		if(!state.playing){
			if(state.players){
				//if(state.players.length === 4){}
				state.players.push(player);
			}
			else{
				state.players = [player];
			}
		}
		else{
			console.error('Cannot join: mission already in progress.');
		}
		return state;
	},
	// Inputs: void
	setup_controls: (state, data) => {
		state.score = {
			completed: [],
			failed: []
		}
		state.players = state.players.sort();
		state.panels = state.players.map((pid, idx) => {
			var panelMap = {}
			var pitfs = PLAYER_ITFS[idx];
			for(var i = 0; i < pitfs.length; i++){
				panelMap[pitfs[i]] = {
					id: pitfs[i],
					dom: 'panel-' + (i + 1),
					active: false,
					tasks: []
				}
			}
			return panelMap;
		});
		state.instructions = state.players.map((pid, idx) => {
			return false;
		});
		return state;
	},
	// Inputs: win, lose, limit
	global_settings: (state, data) => {

		return state;
	},
	// Inputs: void
	start_mission: (state, data) => {
		state.checkedIn = state.checkedIn + 1 || 1;
		if(state.checkedIn === state.players.length){
			state.playing = true;
		}
		return state;
	},
	// Inputs: pid, itf
	start_task: (state, data) => {
		var pidx = getPlayerIndex(state, data.pid);
		var panelMap = state.panels[pidx];
		var tid = chooseTask(data.itf); //Choose tid for the given itf
		var itf = panelMap[data.itf];
		if(itf){
			if(itf.active){
				// Need to handle override case and remove the old instruction from the partner player!
				// Ideally no penalty for this case
			}
			itf.active = true;
			itf.tasks.push(tid);
		}
		else{
			console.error('start_task: could not find interface: ', data.itf);
		}
		return state;
	},
	// Inputs: pid, tid
	start_instruction: (state, data) => {
		var pidx = getPlayerIndex(state, data.pid);
		state.instructions[pidx] = data.tid;
		return state;
	},
	// Inputs: itf, tid
	complete_task: (state, data) => {
		var pidx = getPlayerIndexByInterface(state, data.itf);
		var itf = state.panels[pidx][data.itf];
		if(itf){
			itf.active = false;
		}
		state.score.completed.push(data.tid);
		return state;
	},
	// Inputs: tid
	fail_task: (state, data) => {
		state.score.failed.push(data.tid);
		return state;
	},
	// Inputs: void
	finish_mission: (state, data) => {
		state.playing = false;
		for(var p = 0; p < state.panels.length; p++){
			for(var i in state.panels[p]){
				state.panels[p][i].active = false;
			}
		}
		for(var t = 0; t < state.instructions.length; t++){
			state.instructions[t] = false;
		}
		return state;
	},
	// Inputs: void
	retry: (state, data) => {
		state = {};
		return state;
	}
}<|endoftext|>


<commit_before>#!/usr/bin/env node

var fs = require('fs');
var pt = require('path');
var program = require('commander');
var package = require('../package.json');
var colors = require('colors/safe');
var archy = require('archy');
var async = require('async');

var ren4plex = {

    // config
    config : {
        preview: false,
        recursive: false,
        splitChars : /[.,;!:() _+\-\[\]]/,
        separator : ' ',
        filesToParse : /[^\s]+\.(mkv|m4v|avi|mp4|srt)$/i,
        capitaliseFirstLetter : true,
        parseYear : true,
        parseEpisode: true,
        preReplace : [
            { search: /H.265/gi, replace: '' },
            { search: /H.264/gi, replace: '' },
            { search: /Blu-Ray/gi, replace: '' },
            { search: /S.H.I.E.L.D/gi, replace: 'Shield' },
            { search: /Ac3 2.0/gi, replace: '' },
            { search: /Ac3 5.1/gi, replace: '' },
            { search: /iDN_CreW/gi, replace: '' },
            { search: /Yamato Video/gi, replace: '' },
        ],
        ignoreWords : [
            '','480p','720p','1080p',
            'ITA','ENG','Subs','Sub','iTALiAN','jap','ENGLiSH','Fansub','Softsub','0sec',
            'hdtv','BluRay','DLMux','BDMux','BRMux','DVDRip','BrRip','BdRip','x264','x265','h264','h265','xvid','MP4','WEBRIP','PROPER','WEB','DL','DMux','Mux','Rip','Dvd','Hdtvmux',
            'AC3','Mp3','aac','2CH','DTS','Dts','2HD','HD',
            // releaser
            'TrTd_Team','HEVC','kh','iGM','GiuseppeTnT','Marco','lol','SToNeD','AlgernonWood','NovaRip','Pir8','KILLERS','ZMachine','byR02','DarkSideMux','TeRRa',
            'FoV','IGM','SATOSHi','TLA','RiVER','oRo','by','IperB','rarbg','xXTenGXx','ASAP','TRL','NAHOM','BLUWORLD','Kagome','VTV','Shiv@','QCF','Blackbit', 'Maxks', 'Ruskiyspetz','iCV'
        ]
    },

    // Capitalise the first letter
    capitaliseFirstLetter: function(string) {
        return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
    },

    // Extract extension from filename
    getExtension: function (filename) {
        var i = filename.lastIndexOf('.');
        return (i < 0) ? '' : filename.substr(i);
    },

    // Extract episode string from word (if match)
    getEpisode: function (word, filename){
        var e,s;
        if (filename.indexOf('lol') > -1){
            var int = parseInt(word);
            if (int > 99 && int < 9999){
                s = Math.floor(int / 100);
                e = int - (s * 100);
                return  's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
            }
        }
        // x format (es. 2x11, 1X07)
        var match = /\d{1,2}x\d{2}/i.exec(word);
        if (match){
            var xPos = word.toLowerCase().indexOf('x');
            s = parseInt(word.substr(0,xPos));
            e = parseInt(word.substr(xPos + 1));
            return  's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
        }
        // s format (es. s02e11, S01E07)
        match = /s\d{2}e\d{2}/i.exec(word);
        if (match){
            s = parseInt(word.substr(1,2));
            e = parseInt(word.substr(4));
            return  's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
        }
        return '';
    },

    // Check if the word must be ignored
    isIgnored: function (word){
        word = word.toLowerCase();
        for (var i = 0; i < this.config.ignoreWords.length; i++) {
            if (this.config.ignoreWords[i].toLowerCase() === word){
                return true;
            }
        }
        return false;
    },

    preReplace: function (text){
        var res = text;
        for (var i = 0; i < this.config.preReplace.length; i++) {
            res = res.replace(this.config.preReplace[i].search,this.config.preReplace[i].replace)
        }
        return res;
    },

    // Get new filename
    getFilename: function (filename){
        // find extension
        var i = filename.lastIndexOf('.');
        var extension = (i > -1) ? filename.substr(i) : '';
        if (i > -1) filename = filename.substr(0,i);
        // pre-replace
        filename = this.preReplace(filename);
        // split on words
        var words = filename.split(this.config.splitChars);
        var result = '';
        var year = '';
        var episode = '';
        // cycle on words (from last)
        for (i = words.length - 1; i >= 0; i--) {
            // word not on ignore list
            if (this.isIgnored(words[i])) continue;
            // check if is year
            if (this.config.parseYear && year === ''){
                var int = parseInt(words[i]);
                if (int > 1900 && int <= new Date().getFullYear()){
                    year = this.config.separator + '(' + int + ')';
                    continue;
                }
            }
            // check if episode
            if (this.config.parseEpisode && episode === ''){
                episode = this.getEpisode(words[i], filename);
                if (episode != '') {
                    result = episode + (result.length > 0 ? this.config.separator : '') + result;
                    continue;
                }
            }
            // add word to result
            result =
                (this.config.capitaliseFirstLetter ? this.capitaliseFirstLetter(words[i]) : words[i]) +
                (result.length > 0 ? this.config.separator : '') +
                result;
            ;
        }
        return result + year + extension;
    },

    printFileName: function (oldFile, newFile){
        if (oldFile === newFile)
            return colors.green(oldFile);
        else
            return colors.red(oldFile) + colors.grey(' -> ') + colors.green(newFile);
    },

    // Parse entire directory
    parseDir: function (path, cb){
        fs.readdir(path, function(err, files){
            if (err) {
                cb(null);
                return;
            }
            var result = { label: colors.white(pt.basename(path)), nodes : [] };
            // loop on files
            async.each(files, function(file, cb){
                    var fullpath = pt.join(path,file);
                    // check if recursive and is directory
                    if (ren4plex.config.recursive && fs.lstatSync(fullpath).isDirectory()){
                        ren4plex.parseDir(fullpath, function(data){
                            if (data) result.nodes.push(data);
                            cb();
                        });
                        return;
                    }
                    // check if file name match
                    if (!ren4plex.config.filesToParse.test(file)){
                        cb();
                        return;
                    }
                    // find new name for file
                    var newName = ren4plex.getFilename(file);
                    if (ren4plex.config.preview){
                        result.nodes.push(ren4plex.printFileName(file, newName));
                        cb();
                        return;
                    }
                    // rename file
                    fs.rename(fullpath, pt.join(path,newName), function (err) {
                        if (!err)
                            result.nodes.push(ren4plex.printFileName(file, newName));
                        return cb();
                    });
                },
                function(err){
                    // callback with results
                    return cb(result);
                }
            );
        });
    },

    // Start rename on path
    start: function(path, cb){
        var result = { label: colors.white('ren4plex ')
        + (ren4plex.config.preview ? colors.green('[PREVIEW MODE]') : '')
        + (ren4plex.config.recursive ? colors.green('[RECURSIVE]') : '')
        };
        this.parseDir(path, function (data) {
            result.nodes = [ data ];
            cb(result);
        });
    }

};
module.exports = ren4plex;

program
    .version(package.version, '-v, --version')
    .option('-p, --preview', 'Preview mode (don\'t rename)')
    .option('-r, --recursive', 'Recurse on all sub-directories')
    .usage('[options] [dir]')
    .parse(process.argv);
ren4plex.config.preview = program.preview;
ren4plex.config.recursive = program.recursive;
ren4plex.start(program.args[0] ? program.args[0] : '.', function(result){
    s = archy(result);
    console.log(s);
});
<commit_msg>Add releaser<commit_after>#!/usr/bin/env node

var fs = require('fs');
var pt = require('path');
var program = require('commander');
var package = require('../package.json');
var colors = require('colors/safe');
var archy = require('archy');
var async = require('async');

var ren4plex = {

    // config
    config : {
        preview: false,
        recursive: false,
        splitChars : /[.,;!:() _+\-\[\]]/,
        separator : ' ',
        filesToParse : /[^\s]+\.(mkv|m4v|avi|mp4|srt)$/i,
        capitaliseFirstLetter : true,
        parseYear : true,
        parseEpisode: true,
        preReplace : [
            { search: /H.265/gi, replace: '' },
            { search: /H.264/gi, replace: '' },
            { search: /Blu-Ray/gi, replace: '' },
            { search: /S.H.I.E.L.D/gi, replace: 'Shield' },
            { search: /Ac3 2.0/gi, replace: '' },
            { search: /Ac3 5.1/gi, replace: '' },
            { search: /iDN_CreW/gi, replace: '' },
            { search: /Yamato Video/gi, replace: '' },
        ],
        ignoreWords : [
            '','480p','720p','1080p',
            'ITA','ENG','Subs','Sub','iTALiAN','jap','ENGLiSH','Fansub','Softsub','0sec',
            'hdtv','BluRay','DLMux','BDMux','BRMux','DVDRip','BrRip','BdRip','x264','x265','h264','h265','xvid','MP4','WEBRIP','PROPER','WEB','DL','DMux','Mux','Rip','Dvd','Hdtvmux',
            'AC3','Mp3','aac','2CH','DTS','Dts','2HD','HD',
            // releaser
            'TrTd_Team','HEVC','kh','iGM','GiuseppeTnT','Marco','lol','SToNeD','AlgernonWood','NovaRip','Pir8','KILLERS','ZMachine','byR02','DarkSideMux','TeRRa',
            'FoV','IGM','SATOSHi','TLA','RiVER','oRo','by','IperB','rarbg','xXTenGXx','ASAP','TRL','NAHOM','BLUWORLD','Kagome','VTV','Shiv@','QCF','Blackbit', 'Maxks', 'Ruskiyspetz','iCV','T4P3'
        ]
    },

    // Capitalise the first letter
    capitaliseFirstLetter: function(string) {
        return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
    },

    // Extract extension from filename
    getExtension: function (filename) {
        var i = filename.lastIndexOf('.');
        return (i < 0) ? '' : filename.substr(i);
    },

    // Extract episode string from word (if match)
    getEpisode: function (word, filename){
        var e,s;
        if (filename.indexOf('lol') > -1){
            var int = parseInt(word);
            if (int > 99 && int < 9999){
                s = Math.floor(int / 100);
                e = int - (s * 100);
                return  's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
            }
        }
        // x format (es. 2x11, 1X07)
        var match = /\d{1,2}x\d{2}/i.exec(word);
        if (match){
            var xPos = word.toLowerCase().indexOf('x');
            s = parseInt(word.substr(0,xPos));
            e = parseInt(word.substr(xPos + 1));
            return  's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
        }
        // s format (es. s02e11, S01E07)
        match = /s\d{2}e\d{2}/i.exec(word);
        if (match){
            s = parseInt(word.substr(1,2));
            e = parseInt(word.substr(4));
            return  's' + ('0' + s).slice(-2) + 'e' + ('0' + e).slice(-2);
        }
        return '';
    },

    // Check if the word must be ignored
    isIgnored: function (word){
        word = word.toLowerCase();
        for (var i = 0; i < this.config.ignoreWords.length; i++) {
            if (this.config.ignoreWords[i].toLowerCase() === word){
                return true;
            }
        }
        return false;
    },

    preReplace: function (text){
        var res = text;
        for (var i = 0; i < this.config.preReplace.length; i++) {
            res = res.replace(this.config.preReplace[i].search,this.config.preReplace[i].replace)
        }
        return res;
    },

    // Get new filename
    getFilename: function (filename){
        // find extension
        var i = filename.lastIndexOf('.');
        var extension = (i > -1) ? filename.substr(i) : '';
        if (i > -1) filename = filename.substr(0,i);
        // pre-replace
        filename = this.preReplace(filename);
        // split on words
        var words = filename.split(this.config.splitChars);
        var result = '';
        var year = '';
        var episode = '';
        // cycle on words (from last)
        for (i = words.length - 1; i >= 0; i--) {
            // word not on ignore list
            if (this.isIgnored(words[i])) continue;
            // check if is year
            if (this.config.parseYear && year === ''){
                var int = parseInt(words[i]);
                if (int > 1900 && int <= new Date().getFullYear()){
                    year = this.config.separator + '(' + int + ')';
                    continue;
                }
            }
            // check if episode
            if (this.config.parseEpisode && episode === ''){
                episode = this.getEpisode(words[i], filename);
                if (episode != '') {
                    result = episode + (result.length > 0 ? this.config.separator : '') + result;
                    continue;
                }
            }
            // add word to result
            result =
                (this.config.capitaliseFirstLetter ? this.capitaliseFirstLetter(words[i]) : words[i]) +
                (result.length > 0 ? this.config.separator : '') +
                result;
            ;
        }
        return result + year + extension;
    },

    printFileName: function (oldFile, newFile){
        if (oldFile === newFile)
            return colors.green(oldFile);
        else
            return colors.red(oldFile) + colors.grey(' -> ') + colors.green(newFile);
    },

    // Parse entire directory
    parseDir: function (path, cb){
        fs.readdir(path, function(err, files){
            if (err) {
                cb(null);
                return;
            }
            var result = { label: colors.white(pt.basename(path)), nodes : [] };
            // loop on files
            async.each(files, function(file, cb){
                    var fullpath = pt.join(path,file);
                    // check if recursive and is directory
                    if (ren4plex.config.recursive && fs.lstatSync(fullpath).isDirectory()){
                        ren4plex.parseDir(fullpath, function(data){
                            if (data) result.nodes.push(data);
                            cb();
                        });
                        return;
                    }
                    // check if file name match
                    if (!ren4plex.config.filesToParse.test(file)){
                        cb();
                        return;
                    }
                    // find new name for file
                    var newName = ren4plex.getFilename(file);
                    if (ren4plex.config.preview){
                        result.nodes.push(ren4plex.printFileName(file, newName));
                        cb();
                        return;
                    }
                    // rename file
                    fs.rename(fullpath, pt.join(path,newName), function (err) {
                        if (!err)
                            result.nodes.push(ren4plex.printFileName(file, newName));
                        return cb();
                    });
                },
                function(err){
                    // callback with results
                    return cb(result);
                }
            );
        });
    },

    // Start rename on path
    start: function(path, cb){
        var result = { label: colors.white('ren4plex ')
        + (ren4plex.config.preview ? colors.green('[PREVIEW MODE]') : '')
        + (ren4plex.config.recursive ? colors.green('[RECURSIVE]') : '')
        };
        this.parseDir(path, function (data) {
            result.nodes = [ data ];
            cb(result);
        });
    }

};
module.exports = ren4plex;

program
    .version(package.version, '-v, --version')
    .option('-p, --preview', 'Preview mode (don\'t rename)')
    .option('-r, --recursive', 'Recurse on all sub-directories')
    .usage('[options] [dir]')
    .parse(process.argv);
ren4plex.config.preview = program.preview;
ren4plex.config.recursive = program.recursive;
ren4plex.start(program.args[0] ? program.args[0] : '.', function(result){
    s = archy(result);
    console.log(s);
});
<|endoftext|>