<commit_before>/* Copyright (c) 2010-2013 <NAME> */
"use strict";


var VERSION = '0.5.8'

var common  = require('./common')

var util    = common.util
var events  = common.events
var net     = common.net
var repl    = common.repl
var path    = common.path

var assert   = common.assert
var nid      = common.nid
var async    = common.async
var optimist = common.optimist

var _       = common._
var parambulator = common.parambulator

var arrayify = common.arrayify
var noop     = common.noop
var buffer   = common.buffer




var Router = require('./router').Router
var Entity  = require('./entity').Entity

var store      = require('./store')
var httprouter = require('./http-router')

var argsparser  = require('./argsparser')


// default plugins
var connect_plugin   = require('../plugin/connect')
var mem_store_plugin = require('../plugin/mem-store')

var msgmap  = require('./msgmap')
var logging = require('./logging')


var COUNT = 0



function noopservice( req, res, next ) {
  next && next()
}


function resolvemsg(meta,info) {
  var msg = 'unknown error.'

  if( meta.msg ) {
    msg = meta.msg
  }

  else if( meta.message ) {
    msg = meta.message
  }

  else {
    var code = meta.code

    if( code ) {
      var parts = code.split('/')
      var first = msgmap[parts[0]]

      var infostr = (info?' '+common.owndesc(info,3):'')
      var codeinfo = code + infostr
      msg = first ? (_.isString(first) ? first : (first[ parts[1] ] || codeinfo ) ) : codeinfo
    }
  }

  

  return msg
}


function descerror( meta, error ) {
  if( error ) {
    meta.error   = error
    meta.message = error.message
    meta.where   = stackfirst(error)
  }
  //else throw new Error('no error')
  return meta
}

function stackfirst( error ) {
  return ( error && error.stack && (error.stack+' \n ').split('\n')[1] ) || ''
}



function safe_json_stringify(obj,depth) {
  depth = depth || 0
  var jsonstr = ''
  try {
    jsonstr = JSON.stringify(obj)
  }
  catch( e ) {
    if( 0<depth && ~e.message.indexOf("circular structure") ) {
      jsonstr = "[Circular...]"
    }
    else {
      var sb = []
      for( var k in obj ) {
        sb.push(k+'='+safe_json_stringify(obj[k],depth+1))
      }
      jsonstr = '<'+sb.join(',')+'>'
    }
  }
  return jsonstr
}



// finds the plugin module using require
// the module must be a function
// sets plugindesc.init to be the function exposed by the module
function resolve_plugin( plugindesc, seneca, opts ) {
  seneca.log.debug('register','resolve',common.owndesc(plugindesc,1))

  function try_require(name) {
    if( !name ) return null;
    try {
      plugindesc.searched_paths.push(name)
      var found = use_require(name)
      first_err = null
      return found
    }
    catch(e) {
      first_err = first_err || e
      return undefined;
    }
  }


  if( !plugindesc.name ) {
    throw seneca.fail({code:'seneca/plugin_no_name',desc:plugindesc})
  }

  var m = /^(.+):(.+)$/.exec(plugindesc.name)
  if( m ) {
    plugindesc.name = m[1]
    plugindesc.tag  = m[2]
  }

  if( !plugindesc.init) {
    plugindesc.searched_paths = []
    var name = plugindesc.name
    var tag  = plugindesc.tag
    var fullname = name+(tag?'/'+tag:'')
    var initfunc

    // try to load as a built-in module
    try {
      if( ~name.indexOf('..') || ~name.indexOf('/') ) {
        // yes, control flow. I will burn in Hell.
        throw new Error("not a built-in: '"+name+"', [SKIP]")
      }

      var builtin_path = '../plugin/'+name
      plugindesc.searched_paths.push(builtin_path)
      seneca.log.debug('register','require',builtin_path,fullname)
      initfunc = require(builtin_path)
    }

    catch(e) {
      if( e.message && ( -1 != e.message.indexOf("'"+builtin_path+"'") || ~e.message.indexOf('[SKIP]')) ) {
        var use_require = opts.require || plugindesc.parentmodule.require || require

        // try to load as a seneca repo module
        seneca.log.debug('register','require',fullname)

        var plugin_names = [name,'seneca-'+name,'./'+name]
        var parent_filename = (plugindesc.parentmodule||{}).filename
        var paths = parent_filename ? [ path.dirname(parent_filename) ] : [] 
        paths = _.compact(paths.concat((plugindesc.parentmodule||{}).paths||[]))

        var plugin_paths = plugin_names.slice()
        paths.forEach(function(path){
          plugin_names.forEach(function(name){
            plugin_paths.push(path+'/'+name)
          })
        })

        var first_err
        while( _.isUndefined(initfunc = try_require(plugin_paths.shift())) );
        if( first_err ) throw first_err;
      }
      else throw e;
    }


    if( initfunc ) {
      if( !_.isFunction(initfunc) ) {
        throw seneca.fail({code:'seneca/plugin_no_init_function',plugin:plugindesc})
      }

      plugindesc.init = initfunc
    }
    else {
      seneca.log.debug('plugin','notfound',plugindesc)
    }
  }
}




function Seneca($, opts) {
  // $ is a private context

  function Seneca(){
    events.EventEmitter.call(this)
  }
  util.inherits(Seneca, events.EventEmitter)

  var self = new Seneca()

  self.version = VERSION
  self.id = nid()

  opts.log = opts.log || opts.logger || opts.logging || {}

  opts = deepextend({
    status_interval: 60000
  },opts)


  // legacy api
  if( 'print'==opts.log ) {
    opts.log = {map:[{level:'all',handler:'print'}]}
  }

  // TODO: parambulator validation of opts

  var argv = optimist.argv

  if( process.env['SENECA_LOG'] ) {
    opts.log.map = opts.log.map || []
    var loggingconf = process.env['SENECA_LOG']
    logging.parse_command_line( loggingconf, opts.log.map )
  }

  if( argv.seneca ) {
    if( argv.seneca.log ) {
      opts.log.map = opts.log.map || []
      logging.parse_command_line( argv.seneca.log, opts.log.map )
    }
  }


  if( !opts.log.map ) {
    opts.log.map = [
      {level:'info',handler:'print'},
      {level:'error',handler:'print'}
    ]
  }

  
  $.logrouter = logging.makelogrouter(opts.log)

  self.log = logging.makelog($.logrouter)

    

  $.plugins    = {}
  $.services   = []
  $.actrouter = new Router()

  $.plugin_order = {
    byname:[],
    byref:[],
  }


  $.ready_err = null
  $.ready_queue = async.queue(function(task,done){
    if( task.cb ) {
      task.cb.call(self,function(err){
        if( err ) {
          if( $.ready_err ) {
            $.ready_err.list = $.ready_err.list || []
            $.ready_err.list.push(err)
          }
          else $.ready_err = err;
        }

        done()
      })
    }
    else if( task.ready ) {
      var err = $.ready_err
      $.ready_err = null
      task.ready.call(self,err)
      done()
    }
  },1)




  self.on('error',noop) // prevent process exit



  self.logroute = function(entry,handler){
    entry.handler = handler || entry.handler
    logging.makelogroute(entry,$.logrouter)
  }




  function paramerr(code){
    return function(cb){
      return function(err){ 
        if(err){
          throw self.fail({code:code,msg:err.message})
        }
        else if( cb ) { 
          return cb();
        }
      }
    }
  }

  var paramcheck = {}


  // errfn = required, function; winfn = required, function or boolean, if false, no call
  self.err = function( errfn, winfn ) {
    if( !_.isFunction(errfn) ) throw self.fail({code:'seneca/error_handler_no_err_function'})
    if( !_.isFunction(winfn) && !_.isBoolean(winfn)) throw self.fail({code:'seneca/error_handler_no_win_function'})
    return function(err) {
      if( err  ) {
        return errfn(err)
      }
      else {
        if( _.isFunction(winfn) ) {
          winfn.apply(null,arrayify(arguments,1))
        }
      }
    }
  }






  paramcheck.register = parambulator(
    {type$:'object',required$:['name','init'],string$:['name'],function$:['init','service'],object$:['opts']},
    {topname:'plugin',msgprefix:'register(plugin): ',callbackmaker:paramerr('seneca/register_invalid_plugin')}
  )
  self.register = function( plugin, cbfunc ) {
    cbfunc = _.isFunction(cbfunc) ? cbfunc : noop
    paramcheck.register.validate(plugin)

    var fullname = plugin.name+(plugin.tag?'/'+plugin.tag:'')
    var nameref = [plugin.name,plugin.tag||'-']

    // adjust seneca api to be plugin specific
    var sd = self.delegate()
    sd.log = function(level) {
      var args = arrayify(arguments)

      args.splice(1,0,'plugin',plugin.name,plugin.tag||'-')
      self.log.apply(self,args)
    }
    logging.makelogfuncs(sd)

    sd.fail = function() {
      var args = arrayify(arguments), cbI = -1

      if( _.isObject(args[0]) ) {
        args[0].plugin = fullname
      }

      var cb = _.find(args,function(x){cbI++;return _.isFunction(x)})
      if( cb ) {
        args[cbI] = function(){
          var errarg = arguments[0]
          if( errarg && errarg.seneca ) {
            errarg.seneca.plugin = {name:fullname}
            errarg.seneca.code   = 'unknown'!=errarg.seneca.code ? errarg.seneca.code : 'plugin'
          }
          cb.apply(null,arguments)
        }
      }
      return self.fail.apply(self,args)
    }

    sd.add = function(args,paramspec,actfunc,actmeta) {
      if( _.isFunction(paramspec) ) {
        actmeta = actfunc || {}
        actfunc = paramspec
        paramspec = null
      }
      else {
        actmeta = actmeta || {}
      }
      actmeta.plugin_nameref = nameref
      actmeta.log = sd.log

      self.add.call(sd,args,paramspec,actfunc,actmeta)
    }

    sd.context = {
      module: plugin.parentmodule || module
    }


    return do_register( plugin.opts )

    /*
    self.act({role:'config',cmd:'get',base:fullname,default$:{}},self.err(cbfunc,function(fullname_config){
      var shortname = fullname != plugin.name ? plugin.name : null
      if( !shortname && 0 == fullname.indexOf('seneca-') ) {
        shortname = fullname.substring('seneca-'.length)
      }

      if( shortname ) {
        self.act({role:'config',cmd:'get',base:shortname,default$:{}},self.err(cbfunc,function(name_config){
          var opts = _.extend({},name_config,fullname_config,plugin.opts||{})
          do_register(opts)
        }))
      }
      else return do_register( _.extend({},fullname_config,plugin.opts||{}) )
    }))
     */

    function do_register(opts) {
      self.log.debug('register','init',fullname)
      //$.pushready(fullname)

      function install_plugin(err,meta){
        if( err ) {
          //$.popready(fullname,err,self)
          return cbfunc(err);
        }

        meta = meta || {}

        // legacy api for service function
        if( _.isFunction(meta) ) {
          meta = {service:meta}
        }

        plugin.name    = meta.name    || plugin.name
        plugin.tag     = meta.tag     || plugin.tag || opts.tag$
        plugin.service = meta.service || plugin.service

        nameref[0]=plugin.name
        nameref[1]=plugin.tag

        // name may have been changed by return value from plugin init

        var pluginref = plugin.name+(plugin.tag?'/'+plugin.tag:'')
        $.plugins[pluginref] = plugin

        $.plugin_order.byname.push(plugin.name)
        $.plugin_order.byname = _.uniq($.plugin_order.byname)

        $.plugin_order.byref.push(pluginref)

        var service = plugin.service
        if( service ) {
          service.log = sd.log
          service.key = pluginref
          $.services.push(service)
        }

        self.log.debug('register','install',pluginref,{service:!!service},fullname!=pluginref?fullname:undefined)

        $.ready_queue.push({cb:function(done){
          self.act({init:plugin.name,tag:plugin.tag,default$:{}},function(err,out){
            if( !err ) {
              self.log.debug('register','ready',pluginref,out)
            }
            done(err)
          })
        }})

        cbfunc(null)

        if( void 0 != meta.return ) {
          return meta.return
        }
      }


      // get options
      if( 'options' != plugin.name ) {
        self.act({role:'options',cmd:'get',base:fullname,default$:{}},self.err(cbfunc,function(fullname_options){
          var shortname = fullname != plugin.name ? plugin.name : null
          if( !shortname && 0 == fullname.indexOf('seneca-') ) {
            shortname = fullname.substring('seneca-'.length)
          }
        
          if( shortname ) {
            self.act({role:'options',cmd:'get',base:shortname,default$:{}},self.err(cbfunc,function(name_options){
              var opts = _.extend({},name_options,fullname_options,plugin.opts||{})
              do_init(opts)
            }))
          }
          else do_init( _.extend({},fullname_options,plugin.opts||{}) )
        }))
      }
      else return do_init(plugin.opts)


      function do_init( options ) {
        var args = [options,install_plugin]

        // legacy plugins with function(seneca,opts,cb)
        if( 3 == plugin.init.length ){
          args.unshift(sd)
        }

        // legacy plugins that are async on setup
        var meta = plugin.init.apply(sd,args)

        meta = (plugin.init.length <= 1 && void 0 == meta) ? {} : meta

        if( meta ) {
          return install_plugin(null,meta)
        }
      }
    }
  }



  self.depends = function() {
    var plugin = arguments[0]
    var deps   = arguments[1]
    _.each(deps, function(depname){
      if( !_.contains($.plugin_order.byname,depname) &&
          !_.contains($.plugin_order.byname,'seneca-'+depname) ) {
        self.fail({code:'seneca/plugin_required',plugin:plugin,dependency:depname})
      }
    })
  }



  // FIX: probably does not work
  self.result = function(cb,win){
    if( !_.isFunction(cb) ) throw self.fail({code:'seneca/result_handler_no_callback_function'})
    return function(){
      var args = arrayify(arguments)
      if( args[0] ) {
        self.fail(args[0],cb)
      }
      else {
        if( _.isFunction(win) ) {
          win.apply(null,args.slice(1))
        }
        else cb.apply(null,args);
      }
    }
  }


  // attempts to provide the deepest (i.e. original) exception, with details sub object in seneca property
  // meta: error description, optional
  // cb: callback or flag, optional. flag:true => throw err, flag:false=> return err unthrown
  self.fail = function( meta, cb ) {
    var out, err

    function checkrethrow(e) {
      if( e.seneca ) {
        if( _.isFunction(cb) ) {
          cb(e)
        }
        else return e;
      }
    }

    if( _.isError(meta) ) {
      if( out = checkrethrow(meta) ) return out;
      err = meta
    }

    var argI = 1
    var code = 'unknown'
    var msg  = 'unknown error.'
    var error
    var err_seneca = {}
    var cbfunc = cb

    var why = ''

    if( _.isString(meta) ) {
      code = ''+meta
      msg = resolvemsg({code:code},meta)
      argI = 2
      why = 'string'
    }
    else if( _.isError(meta) ) {
      code = meta.code || 'unknown'
      msg = meta.message
      error = meta
      argI = 2
      why = 'error'
    }
    else if( _.isFunction(meta) ) {
      cbfunc = meta
      why = 'function'
    }
    else if( _.isObject(meta) ) {
      if( meta.error && _.isError(meta.error) ) {
        if( out = checkrethrow(meta) ) return out;
        err = meta.error
      }

      code = meta.code || 'unknown'
      msg = resolvemsg(meta,meta)
      err_seneca = _.extend({},meta)
      argI = 2
      why = 'object'
    }
    else if( void 0 === cb && _.isBoolean(meta) ) {
      cbfunc = meta
      why = 'void'
    }


    err_seneca.when = new Date().toISOString()
    err_seneca.tag  = nid()
    err_seneca.code = code
    if( error ) {
      descerror(err_seneca,error)
    }

    delete err_seneca.msg
    delete err_seneca.message

    try {
      msg = _.template( msg, _.extend({json$:safe_json_stringify},err_seneca) )
    }
    catch( e ) {
      msg = msg + ' (context:'+common.owndesc(err_seneca,3)+')'
    }

    var prefix = "Seneca: "

    msg = ~msg.indexOf(prefix) ? msg : prefix + msg

    if( err ) {
      err.message = msg
    }
    else {
      err = new Error(msg)
    }
    err.seneca = err_seneca

    // remove self.fail calls from stack history
    if( err.stack ) {
      err.stack = err.stack.replace(/\s+at Object\..*?\.fail \([^\r\n]*/g,'')
    }

    var stack = stackfirst(err.seneca.error)
    self.log.error('fail',err.seneca.code,err.message,stack )

    self.emit('error',err)

    if( _.isFunction(cbfunc) ) {
      err.seneca.callback = true
      var cbargs = arrayify(arguments,argI)
      var args = [err].concat(cbargs)
      cbfunc.apply(null,args)
      return err
    }
    else {
      err.seneca.callback = false
      if( void 0 == cbfunc || true === cbfunc ) {
        throw err
      }
      else {
        return err
      }
    }
  }


  // all optional
  self.make = function() {
    var si = (this && this.seneca) ? this : self
    return $.entity.make$.apply(si,arguments)
  }
  self.make$ = self.make


  self.service = function( service ) {
    if( _.isFunction( service ) ) {
      service.log = self.log
      service.key = 'anon-'+uuid().substring(0,8)
      $.services.push(service)
    }
    else {
      return function(req,res,next) {
        function next_service(i) {
          var instance = req.instance || self

          if( i < $.services.length ) {
            var service = $.services[i]

            // TODO need some sort of logging here to trace failures to call next()

            service.call(instance,req,res,function(err){
              if( err ) return next(err);
              next_service(i+1)
            })
          }
          else {
            next && next()
          }
        }
        next_service(0)
      }
    }
  }


  self.httprouter = function(httproutedeffunc) {
    return httproutedeffunc ? httprouter(httproutedeffunc) : noopservice
  }


  // TODO: move to separate file, document!!!

  paramcheck.http = parambulator(
    {type$:'object',required$:['pin','map'],string$:['prefix'],object$:['pin','map']},
    {topname:'spec',msgprefix:'http(spec): ',callbackmaker:paramerr('seneca/http_invalid_spec')})

  self.http = function( spec ) {
    paramcheck.http.validate(spec)
    var prefix = spec.prefix || '/api/'

    if( !prefix.match(/\/+$/) ) {
      prefix += '/'
    }

    var instance = this
    var pin = instance.pin(spec.pin)


    function makedispatch(act,urlspec,handlerspec) {
      return function( req, res, next ) {

        var args = _.extend(
          {},
          _.isObject(req.params)?req.params:{},
          _.isObject(req.query)?req.query:{}
        )

        // data flag means put JSON body into separate data field
        // otherwise mix it all in
        var data = _.isObject(req.body)?req.body:{}
        if( urlspec.data ) {
          args.data = data
        }
        else {
          args = _.extend(data,args)
        }

        // modify args
        for( var argname in spec.args) {
          args[argname] = spec.args[argname](args[argname])
        }


        if( handlerspec.redirect && 'application/x-www-form-urlencoded' == req.headers['content-type']) {

          handlerspec.responder = function(req,res,handlerspec,err,obj) {
            // TODO: put obj into engagement if present
            var url = handlerspec.redirect
            if( err ) {
              url+='?ec='+(err.seneca?err.seneca.code:err.message)
            }
            res.writeHead(302,{
              'Location': url
            })
            res.end()
          }
        }

        var handler   = handlerspec.handler   || defaulthandler
        var responder = handlerspec.responder || defaultresponder

        
        var si = req.seneca || instance
        var respond = function(err,obj){
          responder.call(si,req,res,handlerspec,err,obj)
        }
        

        var act_si = function(args,done){
          act.call(si,args,done)
        }

        handler( req, res, args, act_si, respond, handlerspec)
      }
    }


    function defaulthandler(req,res,args,act,respond) {
      act(args,respond)
    }


    function defaultresponder(req,res,handlerspec,err,obj) {
      var outobj;

      if( _.isObject(obj) ) {
        outobj = _.clone(obj)

        // TODO: test filtering

        var remove_dollar = false
        if( !_.isUndefined(handlerspec.filter) ) {
          if( _.isFunction( handlerspec.filter ) ) {
            outobj = handlerspec.filter(outobj)
          }
          else if( _.isArray( handlerspec.filter ) ) {
            _.each(handlerspec.filter,function(p){
              delete outobj[p]
              remove_dollar = remove_dollar || '$'==p
            })
          }
        }

        // default filter
        // removes $ from entity objects
        else {
          remove_dollar = true
        }

        if( remove_dollar ) {
          _.keys(outobj,function(k){
            if(~k.indexOf('$')){
              delete outobj[k]
            }
          })
        }
      }
      else if( _.isUndefined(obj) ) {
        outobj = ''
      }
      else {
        outobj = obj;
      }


      var objstr = err ? JSON.stringify({error:''+err}) : safe_json_stringify(outobj)
      var code   = err ? (err.seneca ?  err.seneca.status : 500) : 200;

      res.writeHead(code,{
        'Content-Type': 'application/json',
        'Cache-Control': 'private, max-age=0, no-cache, no-store',
        "Content-Length": buffer.Buffer.byteLength(objstr) 
      })
      res.end( objstr )
    }



    var maprouter = httprouter(function(http){
      for( var fname in pin ) {
        var act = pin[fname]
        var url = prefix + fname
        
        var urlspec = spec.map.hasOwnProperty(fname) ? spec.map[fname] : null
        if( !urlspec ) continue;
        
        // METHOD:true abbrev
        urlspec = _.isBoolean(urlspec) ? {} : urlspec

        if( urlspec.alias ) {
          url = prefix + urlspec.alias
        }

        urlspec.suffix = urlspec.suffix || ''

        var mC = 0
        for( var mI = 0; mI < httprouter.methods.length; mI++ ) {
          var m = httprouter.methods[mI]

          var handler = urlspec[m] || urlspec[m.toUpperCase()]
          if( handler ) {
            var handlerspec = _.isObject(handler) ? handler : {}
            handlerspec.handler = handlerspec.handler || (_.isFunction(handler) ? handler : defaulthandler)
            var dispatch = makedispatch(act,urlspec,handlerspec)
            var fullurl = url+urlspec.suffix
            self.log.debug('http',m,fullurl)
            http[m](fullurl, dispatch)
            mC++
          }
        }

        if( 0 == mC ) {
          var dispatch = makedispatch(act,urlspec,{})
          var fullurl = url+urlspec.suffix
          self.log.debug('http','get',fullurl)
          http.get(fullurl, dispatch)
        }
      }
    })

    
    return function(req,res,next) {
      var si = req.seneca || instance

      if( spec.preware ) {
        spec.preware.call(si,req,res,do_maprouter)
      }
      else do_maprouter()

      function do_maprouter(err) {
        if(err ) return next(err);

        maprouter(req,res,function(err){
          if(err ) return next(err);

          if( spec.postware ) {
            spec.postware.call(si,req,res,next)
          }
          else next()
        })
      }
    }
  }




  // get plugin instance
  self.findplugin = function(plugindesc,tag) {
    var name = plugindesc.name || plugindesc
    var tag  = plugindesc.tag  || tag

    var key = name+(tag?'/'+tag:'')
    var plugin = $.plugins[key]

    return plugin
  }
<commit_msg>preware only runs if prefix matches<commit_after>/* Copyright (c) 2010-2013 <NAME> */
"use strict";


var VERSION = '0.5.8'

var common  = require('./common')

var util    = common.util
var events  = common.events
var net     = common.net
var repl    = common.repl
var path    = common.path

var assert   = common.assert
var nid      = common.nid
var async    = common.async
var optimist = common.optimist

var _       = common._
var parambulator = common.parambulator

var arrayify = common.arrayify
var noop     = common.noop
var buffer   = common.buffer




var Router = require('./router').Router
var Entity  = require('./entity').Entity

var store      = require('./store')
var httprouter = require('./http-router')

var argsparser  = require('./argsparser')


// default plugins
var connect_plugin   = require('../plugin/connect')
var mem_store_plugin = require('../plugin/mem-store')

var msgmap  = require('./msgmap')
var logging = require('./logging')


var COUNT = 0



function noopservice( req, res, next ) {
  next && next()
}


function resolvemsg(meta,info) {
  var msg = 'unknown error.'

  if( meta.msg ) {
    msg = meta.msg
  }

  else if( meta.message ) {
    msg = meta.message
  }

  else {
    var code = meta.code

    if( code ) {
      var parts = code.split('/')
      var first = msgmap[parts[0]]

      var infostr = (info?' '+common.owndesc(info,3):'')
      var codeinfo = code + infostr
      msg = first ? (_.isString(first) ? first : (first[ parts[1] ] || codeinfo ) ) : codeinfo
    }
  }

  

  return msg
}


function descerror( meta, error ) {
  if( error ) {
    meta.error   = error
    meta.message = error.message
    meta.where   = stackfirst(error)
  }
  //else throw new Error('no error')
  return meta
}

function stackfirst( error ) {
  return ( error && error.stack && (error.stack+' \n ').split('\n')[1] ) || ''
}



function safe_json_stringify(obj,depth) {
  depth = depth || 0
  var jsonstr = ''
  try {
    jsonstr = JSON.stringify(obj)
  }
  catch( e ) {
    if( 0<depth && ~e.message.indexOf("circular structure") ) {
      jsonstr = "[Circular...]"
    }
    else {
      var sb = []
      for( var k in obj ) {
        sb.push(k+'='+safe_json_stringify(obj[k],depth+1))
      }
      jsonstr = '<'+sb.join(',')+'>'
    }
  }
  return jsonstr
}



// finds the plugin module using require
// the module must be a function
// sets plugindesc.init to be the function exposed by the module
function resolve_plugin( plugindesc, seneca, opts ) {
  seneca.log.debug('register','resolve',common.owndesc(plugindesc,1))

  function try_require(name) {
    if( !name ) return null;
    try {
      plugindesc.searched_paths.push(name)
      var found = use_require(name)
      first_err = null
      return found
    }
    catch(e) {
      first_err = first_err || e
      return undefined;
    }
  }


  if( !plugindesc.name ) {
    throw seneca.fail({code:'seneca/plugin_no_name',desc:plugindesc})
  }

  var m = /^(.+):(.+)$/.exec(plugindesc.name)
  if( m ) {
    plugindesc.name = m[1]
    plugindesc.tag  = m[2]
  }

  if( !plugindesc.init) {
    plugindesc.searched_paths = []
    var name = plugindesc.name
    var tag  = plugindesc.tag
    var fullname = name+(tag?'/'+tag:'')
    var initfunc

    // try to load as a built-in module
    try {
      if( ~name.indexOf('..') || ~name.indexOf('/') ) {
        // yes, control flow. I will burn in Hell.
        throw new Error("not a built-in: '"+name+"', [SKIP]")
      }

      var builtin_path = '../plugin/'+name
      plugindesc.searched_paths.push(builtin_path)
      seneca.log.debug('register','require',builtin_path,fullname)
      initfunc = require(builtin_path)
    }

    catch(e) {
      if( e.message && ( -1 != e.message.indexOf("'"+builtin_path+"'") || ~e.message.indexOf('[SKIP]')) ) {
        var use_require = opts.require || plugindesc.parentmodule.require || require

        // try to load as a seneca repo module
        seneca.log.debug('register','require',fullname)

        var plugin_names = [name,'seneca-'+name,'./'+name]
        var parent_filename = (plugindesc.parentmodule||{}).filename
        var paths = parent_filename ? [ path.dirname(parent_filename) ] : [] 
        paths = _.compact(paths.concat((plugindesc.parentmodule||{}).paths||[]))

        var plugin_paths = plugin_names.slice()
        paths.forEach(function(path){
          plugin_names.forEach(function(name){
            plugin_paths.push(path+'/'+name)
          })
        })

        var first_err
        while( _.isUndefined(initfunc = try_require(plugin_paths.shift())) );
        if( first_err ) throw first_err;
      }
      else throw e;
    }


    if( initfunc ) {
      if( !_.isFunction(initfunc) ) {
        throw seneca.fail({code:'seneca/plugin_no_init_function',plugin:plugindesc})
      }

      plugindesc.init = initfunc
    }
    else {
      seneca.log.debug('plugin','notfound',plugindesc)
    }
  }
}




function Seneca($, opts) {
  // $ is a private context

  function Seneca(){
    events.EventEmitter.call(this)
  }
  util.inherits(Seneca, events.EventEmitter)

  var self = new Seneca()

  self.version = VERSION
  self.id = nid()

  opts.log = opts.log || opts.logger || opts.logging || {}

  opts = deepextend({
    status_interval: 60000
  },opts)


  // legacy api
  if( 'print'==opts.log ) {
    opts.log = {map:[{level:'all',handler:'print'}]}
  }

  // TODO: parambulator validation of opts

  var argv = optimist.argv

  if( process.env['SENECA_LOG'] ) {
    opts.log.map = opts.log.map || []
    var loggingconf = process.env['SENECA_LOG']
    logging.parse_command_line( loggingconf, opts.log.map )
  }

  if( argv.seneca ) {
    if( argv.seneca.log ) {
      opts.log.map = opts.log.map || []
      logging.parse_command_line( argv.seneca.log, opts.log.map )
    }
  }


  if( !opts.log.map ) {
    opts.log.map = [
      {level:'info',handler:'print'},
      {level:'error',handler:'print'}
    ]
  }

  
  $.logrouter = logging.makelogrouter(opts.log)

  self.log = logging.makelog($.logrouter)

    

  $.plugins    = {}
  $.services   = []
  $.actrouter = new Router()

  $.plugin_order = {
    byname:[],
    byref:[],
  }


  $.ready_err = null
  $.ready_queue = async.queue(function(task,done){
    if( task.cb ) {
      task.cb.call(self,function(err){
        if( err ) {
          if( $.ready_err ) {
            $.ready_err.list = $.ready_err.list || []
            $.ready_err.list.push(err)
          }
          else $.ready_err = err;
        }

        done()
      })
    }
    else if( task.ready ) {
      var err = $.ready_err
      $.ready_err = null
      task.ready.call(self,err)
      done()
    }
  },1)




  self.on('error',noop) // prevent process exit



  self.logroute = function(entry,handler){
    entry.handler = handler || entry.handler
    logging.makelogroute(entry,$.logrouter)
  }




  function paramerr(code){
    return function(cb){
      return function(err){ 
        if(err){
          throw self.fail({code:code,msg:err.message})
        }
        else if( cb ) { 
          return cb();
        }
      }
    }
  }

  var paramcheck = {}


  // errfn = required, function; winfn = required, function or boolean, if false, no call
  self.err = function( errfn, winfn ) {
    if( !_.isFunction(errfn) ) throw self.fail({code:'seneca/error_handler_no_err_function'})
    if( !_.isFunction(winfn) && !_.isBoolean(winfn)) throw self.fail({code:'seneca/error_handler_no_win_function'})
    return function(err) {
      if( err  ) {
        return errfn(err)
      }
      else {
        if( _.isFunction(winfn) ) {
          winfn.apply(null,arrayify(arguments,1))
        }
      }
    }
  }






  paramcheck.register = parambulator(
    {type$:'object',required$:['name','init'],string$:['name'],function$:['init','service'],object$:['opts']},
    {topname:'plugin',msgprefix:'register(plugin): ',callbackmaker:paramerr('seneca/register_invalid_plugin')}
  )
  self.register = function( plugin, cbfunc ) {
    cbfunc = _.isFunction(cbfunc) ? cbfunc : noop
    paramcheck.register.validate(plugin)

    var fullname = plugin.name+(plugin.tag?'/'+plugin.tag:'')
    var nameref = [plugin.name,plugin.tag||'-']

    // adjust seneca api to be plugin specific
    var sd = self.delegate()
    sd.log = function(level) {
      var args = arrayify(arguments)

      args.splice(1,0,'plugin',plugin.name,plugin.tag||'-')
      self.log.apply(self,args)
    }
    logging.makelogfuncs(sd)

    sd.fail = function() {
      var args = arrayify(arguments), cbI = -1

      if( _.isObject(args[0]) ) {
        args[0].plugin = fullname
      }

      var cb = _.find(args,function(x){cbI++;return _.isFunction(x)})
      if( cb ) {
        args[cbI] = function(){
          var errarg = arguments[0]
          if( errarg && errarg.seneca ) {
            errarg.seneca.plugin = {name:fullname}
            errarg.seneca.code   = 'unknown'!=errarg.seneca.code ? errarg.seneca.code : 'plugin'
          }
          cb.apply(null,arguments)
        }
      }
      return self.fail.apply(self,args)
    }

    sd.add = function(args,paramspec,actfunc,actmeta) {
      if( _.isFunction(paramspec) ) {
        actmeta = actfunc || {}
        actfunc = paramspec
        paramspec = null
      }
      else {
        actmeta = actmeta || {}
      }
      actmeta.plugin_nameref = nameref
      actmeta.log = sd.log

      self.add.call(sd,args,paramspec,actfunc,actmeta)
    }

    sd.context = {
      module: plugin.parentmodule || module
    }


    return do_register( plugin.opts )

    /*
    self.act({role:'config',cmd:'get',base:fullname,default$:{}},self.err(cbfunc,function(fullname_config){
      var shortname = fullname != plugin.name ? plugin.name : null
      if( !shortname && 0 == fullname.indexOf('seneca-') ) {
        shortname = fullname.substring('seneca-'.length)
      }

      if( shortname ) {
        self.act({role:'config',cmd:'get',base:shortname,default$:{}},self.err(cbfunc,function(name_config){
          var opts = _.extend({},name_config,fullname_config,plugin.opts||{})
          do_register(opts)
        }))
      }
      else return do_register( _.extend({},fullname_config,plugin.opts||{}) )
    }))
     */

    function do_register(opts) {
      self.log.debug('register','init',fullname)
      //$.pushready(fullname)

      function install_plugin(err,meta){
        if( err ) {
          //$.popready(fullname,err,self)
          return cbfunc(err);
        }

        meta = meta || {}

        // legacy api for service function
        if( _.isFunction(meta) ) {
          meta = {service:meta}
        }

        plugin.name    = meta.name    || plugin.name
        plugin.tag     = meta.tag     || plugin.tag || opts.tag$
        plugin.service = meta.service || plugin.service

        nameref[0]=plugin.name
        nameref[1]=plugin.tag

        // name may have been changed by return value from plugin init

        var pluginref = plugin.name+(plugin.tag?'/'+plugin.tag:'')
        $.plugins[pluginref] = plugin

        $.plugin_order.byname.push(plugin.name)
        $.plugin_order.byname = _.uniq($.plugin_order.byname)

        $.plugin_order.byref.push(pluginref)

        var service = plugin.service
        if( service ) {
          service.log = sd.log
          service.key = pluginref
          $.services.push(service)
        }

        self.log.debug('register','install',pluginref,{service:!!service},fullname!=pluginref?fullname:undefined)

        $.ready_queue.push({cb:function(done){
          self.act({init:plugin.name,tag:plugin.tag,default$:{}},function(err,out){
            if( !err ) {
              self.log.debug('register','ready',pluginref,out)
            }
            done(err)
          })
        }})

        cbfunc(null)

        if( void 0 != meta.return ) {
          return meta.return
        }
      }


      // get options
      if( 'options' != plugin.name ) {
        self.act({role:'options',cmd:'get',base:fullname,default$:{}},self.err(cbfunc,function(fullname_options){
          var shortname = fullname != plugin.name ? plugin.name : null
          if( !shortname && 0 == fullname.indexOf('seneca-') ) {
            shortname = fullname.substring('seneca-'.length)
          }
        
          if( shortname ) {
            self.act({role:'options',cmd:'get',base:shortname,default$:{}},self.err(cbfunc,function(name_options){
              var opts = _.extend({},name_options,fullname_options,plugin.opts||{})
              do_init(opts)
            }))
          }
          else do_init( _.extend({},fullname_options,plugin.opts||{}) )
        }))
      }
      else return do_init(plugin.opts)


      function do_init( options ) {
        var args = [options,install_plugin]

        // legacy plugins with function(seneca,opts,cb)
        if( 3 == plugin.init.length ){
          args.unshift(sd)
        }

        // legacy plugins that are async on setup
        var meta = plugin.init.apply(sd,args)

        meta = (plugin.init.length <= 1 && void 0 == meta) ? {} : meta

        if( meta ) {
          return install_plugin(null,meta)
        }
      }
    }
  }



  self.depends = function() {
    var plugin = arguments[0]
    var deps   = arguments[1]
    _.each(deps, function(depname){
      if( !_.contains($.plugin_order.byname,depname) &&
          !_.contains($.plugin_order.byname,'seneca-'+depname) ) {
        self.fail({code:'seneca/plugin_required',plugin:plugin,dependency:depname})
      }
    })
  }



  // FIX: probably does not work
  self.result = function(cb,win){
    if( !_.isFunction(cb) ) throw self.fail({code:'seneca/result_handler_no_callback_function'})
    return function(){
      var args = arrayify(arguments)
      if( args[0] ) {
        self.fail(args[0],cb)
      }
      else {
        if( _.isFunction(win) ) {
          win.apply(null,args.slice(1))
        }
        else cb.apply(null,args);
      }
    }
  }


  // attempts to provide the deepest (i.e. original) exception, with details sub object in seneca property
  // meta: error description, optional
  // cb: callback or flag, optional. flag:true => throw err, flag:false=> return err unthrown
  self.fail = function( meta, cb ) {
    var out, err

    function checkrethrow(e) {
      if( e.seneca ) {
        if( _.isFunction(cb) ) {
          cb(e)
        }
        else return e;
      }
    }

    if( _.isError(meta) ) {
      if( out = checkrethrow(meta) ) return out;
      err = meta
    }

    var argI = 1
    var code = 'unknown'
    var msg  = 'unknown error.'
    var error
    var err_seneca = {}
    var cbfunc = cb

    var why = ''

    if( _.isString(meta) ) {
      code = ''+meta
      msg = resolvemsg({code:code},meta)
      argI = 2
      why = 'string'
    }
    else if( _.isError(meta) ) {
      code = meta.code || 'unknown'
      msg = meta.message
      error = meta
      argI = 2
      why = 'error'
    }
    else if( _.isFunction(meta) ) {
      cbfunc = meta
      why = 'function'
    }
    else if( _.isObject(meta) ) {
      if( meta.error && _.isError(meta.error) ) {
        if( out = checkrethrow(meta) ) return out;
        err = meta.error
      }

      code = meta.code || 'unknown'
      msg = resolvemsg(meta,meta)
      err_seneca = _.extend({},meta)
      argI = 2
      why = 'object'
    }
    else if( void 0 === cb && _.isBoolean(meta) ) {
      cbfunc = meta
      why = 'void'
    }


    err_seneca.when = new Date().toISOString()
    err_seneca.tag  = nid()
    err_seneca.code = code
    if( error ) {
      descerror(err_seneca,error)
    }

    delete err_seneca.msg
    delete err_seneca.message

    try {
      msg = _.template( msg, _.extend({json$:safe_json_stringify},err_seneca) )
    }
    catch( e ) {
      msg = msg + ' (context:'+common.owndesc(err_seneca,3)+')'
    }

    var prefix = "Seneca: "

    msg = ~msg.indexOf(prefix) ? msg : prefix + msg

    if( err ) {
      err.message = msg
    }
    else {
      err = new Error(msg)
    }
    err.seneca = err_seneca

    // remove self.fail calls from stack history
    if( err.stack ) {
      err.stack = err.stack.replace(/\s+at Object\..*?\.fail \([^\r\n]*/g,'')
    }

    var stack = stackfirst(err.seneca.error)
    self.log.error('fail',err.seneca.code,err.message,stack )

    self.emit('error',err)

    if( _.isFunction(cbfunc) ) {
      err.seneca.callback = true
      var cbargs = arrayify(arguments,argI)
      var args = [err].concat(cbargs)
      cbfunc.apply(null,args)
      return err
    }
    else {
      err.seneca.callback = false
      if( void 0 == cbfunc || true === cbfunc ) {
        throw err
      }
      else {
        return err
      }
    }
  }


  // all optional
  self.make = function() {
    var si = (this && this.seneca) ? this : self
    return $.entity.make$.apply(si,arguments)
  }
  self.make$ = self.make


  self.service = function( service ) {
    if( _.isFunction( service ) ) {
      service.log = self.log
      service.key = 'anon-'+uuid().substring(0,8)
      $.services.push(service)
    }
    else {
      return function(req,res,next) {
        function next_service(i) {
          var instance = req.instance || self

          if( i < $.services.length ) {
            var service = $.services[i]

            // TODO need some sort of logging here to trace failures to call next()

            service.call(instance,req,res,function(err){
              if( err ) return next(err);
              next_service(i+1)
            })
          }
          else {
            next && next()
          }
        }
        next_service(0)
      }
    }
  }


  self.httprouter = function(httproutedeffunc) {
    return httproutedeffunc ? httprouter(httproutedeffunc) : noopservice
  }


  // TODO: move to separate file, document!!!

  paramcheck.http = parambulator(
    {type$:'object',required$:['pin','map'],string$:['prefix'],object$:['pin','map']},
    {topname:'spec',msgprefix:'http(spec): ',callbackmaker:paramerr('seneca/http_invalid_spec')})

  self.http = function( spec ) {
    paramcheck.http.validate(spec)
    var prefix = spec.prefix || '/api/'


    if( !prefix.match(/\/+$/) ) {
      prefix += '/'
    }

    var instance = this
    var pin = instance.pin(spec.pin)


    function makedispatch(act,urlspec,handlerspec) {
      return function( req, res, next ) {

        var args = _.extend(
          {},
          _.isObject(req.params)?req.params:{},
          _.isObject(req.query)?req.query:{}
        )

        // data flag means put JSON body into separate data field
        // otherwise mix it all in
        var data = _.isObject(req.body)?req.body:{}
        if( urlspec.data ) {
          args.data = data
        }
        else {
          args = _.extend(data,args)
        }

        // modify args
        for( var argname in spec.args) {
          args[argname] = spec.args[argname](args[argname])
        }


        if( handlerspec.redirect && 'application/x-www-form-urlencoded' == req.headers['content-type']) {

          handlerspec.responder = function(req,res,handlerspec,err,obj) {
            // TODO: put obj into engagement if present
            var url = handlerspec.redirect
            if( err ) {
              url+='?ec='+(err.seneca?err.seneca.code:err.message)
            }
            res.writeHead(302,{
              'Location': url
            })
            res.end()
          }
        }

        var handler   = handlerspec.handler   || defaulthandler
        var responder = handlerspec.responder || defaultresponder

        
        var si = req.seneca || instance
        var respond = function(err,obj){
          responder.call(si,req,res,handlerspec,err,obj)
        }
        

        var act_si = function(args,done){
          act.call(si,args,done)
        }

        var preware = spec.preware || function(){arguments[2]()}

        preware.call(si,req,res,function(err){
          if(err ) return next(err);
          handler.call( si, req, res, args, act_si, respond, handlerspec)
        })
      }
    }


    function defaulthandler(req,res,args,act,respond) {
      act(args,respond)
    }


    function defaultresponder(req,res,handlerspec,err,obj) {
      var outobj;

      if( _.isObject(obj) ) {
        outobj = _.clone(obj)

        // TODO: test filtering

        var remove_dollar = false
        if( !_.isUndefined(handlerspec.filter) ) {
          if( _.isFunction( handlerspec.filter ) ) {
            outobj = handlerspec.filter(outobj)
          }
          else if( _.isArray( handlerspec.filter ) ) {
            _.each(handlerspec.filter,function(p){
              delete outobj[p]
              remove_dollar = remove_dollar || '$'==p
            })
          }
        }

        // default filter
        // removes $ from entity objects
        else {
          remove_dollar = true
        }

        if( remove_dollar ) {
          _.keys(outobj,function(k){
            if(~k.indexOf('$')){
              delete outobj[k]
            }
          })
        }
      }
      else if( _.isUndefined(obj) ) {
        outobj = ''
      }
      else {
        outobj = obj;
      }


      var objstr = err ? JSON.stringify({error:''+err}) : safe_json_stringify(outobj)
      var code   = err ? (err.seneca ?  err.seneca.status : 500) : 200;

      res.writeHead(code,{
        'Content-Type': 'application/json',
        'Cache-Control': 'private, max-age=0, no-cache, no-store',
        "Content-Length": buffer.Buffer.byteLength(objstr) 
      })
      res.end( objstr )
    }



    var maprouter = httprouter(function(http){
      for( var fname in pin ) {
        var act = pin[fname]
        var url = prefix + fname
        
        var urlspec = spec.map.hasOwnProperty(fname) ? spec.map[fname] : null
        if( !urlspec ) continue;
        
        // METHOD:true abbrev
        urlspec = _.isBoolean(urlspec) ? {} : urlspec

        if( urlspec.alias ) {
          url = prefix + urlspec.alias
        }

        urlspec.suffix = urlspec.suffix || ''

        var mC = 0
        for( var mI = 0; mI < httprouter.methods.length; mI++ ) {
          var m = httprouter.methods[mI]

          var handler = urlspec[m] || urlspec[m.toUpperCase()]
          if( handler ) {
            var handlerspec = _.isObject(handler) ? handler : {}
            handlerspec.handler = handlerspec.handler || (_.isFunction(handler) ? handler : defaulthandler)
            var dispatch = makedispatch(act,urlspec,handlerspec)
            var fullurl = url+urlspec.suffix
            self.log.debug('http',m,fullurl)
            http[m](fullurl, dispatch)
            mC++
          }
        }

        if( 0 == mC ) {
          var dispatch = makedispatch(act,urlspec,{})
          var fullurl = url+urlspec.suffix
          self.log.debug('http','get',fullurl)
          http.get(fullurl, dispatch)
        }
      }

      
      // lastly, try preware by itself against prefix if nothing else matches
      // needed for common auth checks etc
      // ensures preware is always called
      if( spec.preware ) {
        http.get(prefix, spec.preware)
      }
    })

    
    return function(req,res,next) {
      var si = req.seneca || instance

      maprouter(req,res,function(err){
        if(err ) return next(err);


        if( spec.postware ) {
          spec.postware.call(si,req,res,next)
        }
        else next()
      })
    }
  }




  // get plugin instance
  self.findplugin = function(plugindesc,tag) {
    var name = plugindesc.name || plugindesc
    var tag  = plugindesc.tag  || tag

    var key = name+(tag?'/'+tag:'')
    var plugin = $.plugins[key]

    return plugin
  }

{'fraction_non_alphanumeric': 0.14413592686082768, 'fraction_numerical': 0.003118172369174639, 'mean_word_length': 2.0908018094801024, 'pattern_counts': {'":': 2, '<': 15, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6762222', 'n_tokens_mistral': 15583, 'n_tokens_neox': 15030, 'n_words': 2970}