<commit_before>"use strict"

const argv = new Set(process.argv.slice(2))
const NODE_ENV = [...argv].filter(v => v.indexOf("--NODE_ENV=") == 0 || v.indexOf("--env=") == 0)
                          .map(v => v.split("=")[1])[0] || null

const { exec, fork, spawn, spawnSync } = require("child_process")
const { createReadStream, createWriteStream, mkdir, watch } = require("fs")
const { dirname, resolve:resolvePath, join:joinPath } = require("path")
const { createInterface:createReadlineInterface } = require("readline")
const { inherits } = require("util")
const { nextTick } = process
const { typeOf } = require("ippankiban/lib/type")
const { networkInterfaces } = require("os")

const { class:klass } = require("ippankiban/lib/class")
const { Node } = require("ippankiban/lib/Node")
const { UID:{uid} } = require("ippankiban/lib/UID")
const { UnixSocketServer } = require("ippanserver/lib/UnixSocketServer")

const cwd = resolvePath(process.cwd(), dirname(process.mainModule.filename))

module.exports.App = klass(Node, statics => {
    const apps = new WeakMap
    const TMP = "/tmp" //TODO find alternatives on alternative OS

    const nl_comment_at_0 = char => char == "#" || char == ";"

    return {
        constructor: function(){
            Node.call(this)
            apps.set(this, new Map)

            Promise.all([
                new Promise(resolve => {
                  const mandatories = new Set()

                  const input = createReadStream( resolvePath(cwd, "./.flags") )
                  const lines = createReadlineInterface({ input })

                  const online = line => {
                      line = line.trim()
                      if ( nl_comment_at_0(line[0]) || !line.length )
                        return

                      const parts = line.split(/\s/)
                                        .filter(v => !!v)

                      parts.forEach(part => mandatories.add(part))
                  }

                  const onend = () => {
                      input.removeListener("error", onerror)
                      lines.removeListener("error", onerror)
                      lines.removeListener("line", online)
                      lines.removeListener("close", onend)
                      resolve(mandatories)
                  }

                  const onerror = e => {
                      if ( e && e.code == "ENOENT" )
                        onend()
                      else {
                        throw e
                      }
                  }

                  input.addListener("error", onerror)
                  lines.addListener("error", onerror)
                  lines.addListener("line", online)
                  lines.addListener("close", onend)
                }).then(mandatories => new Promise(resolve => {
                      const execArgv = new Set(process.execArgv)
                      let ok = true

                      mandatories.forEach(arg => {
                          if ( execArgv.has(arg) )
                            return
                          ok = false
                          execArgv.add(arg)
                      })

                      if ( ok )
                        return resolve()

                      const cp = fork(resolvePath(cwd, process.mainModule.filename), process.argv.slice(2), {execArgv: [...execArgv]})

                      process.addListener("SIGINT", () => cp.kill())
                      process.addListener("SIGTERM", () => cp.kill())
                      process.addListener("exit", () => cp.kill())
                      cp.addListener("SIGINT", () => process.exit())
                      cp.addListener("SIGTERM", () => process.exit())
                      cp.addListener("exit", () => process.exit())

                      const e = new Error("missing mandatory parameters, restarting with child process")
                      e.warn = true
                      throw e
                  }))
            ])
            .then(() => new Promise(resolve => {
                //process.setMaxListeners(10)
                process.on("SIGINT", e => process.exit())
                process.on("SIGTERM", e => process.exit())

                resolve()
            }))
            .then(() => new Promise(resolve => {
                /*
                    project environment configuration
                */
                const input = createReadStream( resolvePath(cwd, "./.env") )
                const lines = createReadlineInterface({ input })
                const variables = new Map
                const pairs = new Map

                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return
                    else if ( line[0] == "$" ) {
                        const idx = line.indexOf("=")
                        variables.set(line.slice(1, idx).trim(), line.slice(idx+1).trim())
                    }
                    else {
                        const idx = line.indexOf("=")
                        const key = line.slice(0, idx).trim()
                        let value = line.slice(idx+1).trim()

                        if ( value[0] == "[" && value[value.length-1] == "]")
                            value = value.slice(1, -1)
                                         .split(",")
                                         .map(v => v.trim())

                        pairs.set(key, value)
                    }
                }

                const onend = () => {
                    input.removeListener("error", onerror)
                    lines.removeListener("error", onerror)
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)

                    pairs.forEach((value, key) => {
                        variables.forEach((replace, match) => {
                            if ( typeOf(value) == "string" )
                              value = value.replace(new RegExp(`\\$${match}`, "g"), replace)
                            else if ( typeOf(value) == "array" )
                              value = value.map(v => v.replace(new RegExp(`\\$${match}`, "g"), replace))
                        })

                        process.env[key.toUpperCase()] = value
                    })

                    variables.clear()
                    pairs.clear()
                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => new Promise(resolve => {
                /*
                    user config, can be present or not
                */
                const input = createReadStream( resolvePath(cwd, "./.local") )
                const lines = createReadlineInterface({ input })
                const variables = new Map
                const pairs = new Map

                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return
                    else if ( line[0] == "$" ) {
                        const idx = line.indexOf("=")
                        variables.set(line.slice(1, idx).trim(), line.slice(idx+1).trim())
                    }
                    else {
                        const idx = line.indexOf("=")
                        const key = line.slice(0, idx).trim()
                        let value = line.slice(idx+1).trim()

                        if ( value[0] == "[" && value[value.length-1] == "]")
                            value = value.slice(1, -1)
                                         .split(",")
                                         .map(v => v.trim())

                        pairs.set(key, value)
                    }
                }

                const onend = () => {
                    input.removeListener("error", onerror)
                    lines.removeListener("error", onerror)
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)

                    pairs.forEach((value, key) => {
                        variables.forEach((replace, match) => {
                            if ( typeOf(value) == "string" )
                              value = value.replace(new RegExp(`\\$${match}`, "g"), replace)
                            else if ( typeOf(value) == "array" )
                              value = value.map(v => v.replace(new RegExp(`\\$${match}`, "g"), replace))
                        })

                        process.env[key.toUpperCase()] = value
                    })

                    variables.clear()
                    pairs.clear()

                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => new Promise(resolve => {
                apps.get(this).set("uid", uid())

                resolve()
            }))
            .then(() => new Promise(resolve => {
                const mode = 0O0775 & (~process.umask())
                const ifaces = networkInterfaces()

                process.env.NODE_ENV = process.env.NODE_ENV  || NODE_ENV || "dev"
                process.env.ROOT = cwd
                process.env.TMP = this.tmp_path
                process.env.APP_SOCKET = this.socket_path

                Object.keys(ifaces).forEach(ifname => {
                    ifaces[ifname].forEach(({address, family}) => {
                        if ( family == "IPv4" && address !== "127.0.0.1" )
                          process.env.IPV4 = address
                    })
                })

                mkdir(process.env.TMP, mode, err => {
                    if ( err ) throw err

                    process.addListener("SIGINT", () => process.exit())
                    process.addListener("SIGTERM", () => process.exit())
                    process.addListener("exit", code => {
                        console.log(`[ippanAppManager] attempting to delete ${process.env.TMP}`)
                        spawnSync("rm", ["-rf", process.env.TMP])
                    })

                    resolve()
                })
            }))
            .then(() => new Promise(resolve => {
                apps.get(this).set("unixSocketServer", new UnixSocketServer(this.socket_path))
                this.appendChild(this.unixSocketServer)

                this.unixSocketServer.addEventListener("listening", e => {
                    console.log(`app socket listening (${this.unixSocketServer.socket})`)
                    this.dispatchEvent("unixsocketserver")
                    resolve()
                })
            }))
            .then(() => new Promise((resolve, reject) => {
                const input = createReadStream( resolvePath(cwd, ".children") )
                const lines = createReadlineInterface({ input })

                let processing = Promise.resolve()
                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return

                    processing = processing.then(() => new Promise(resolve => {
                        const parts = line.split(/\s|\[|\]|,/gi)
                                          .filter(v => !!v)
                                          .map(v => joinPath(cwd ,v.trim()))
                        const main = parts.shift()
                        const to_watch = parts

                        let queue = Promise.resolve()
                        const start_child = prev_cp => new Promise(resolve => {
                            const start = () => {
                                const cp = fork(main, process.argv.slice(2), { env: process.env })
                                console.log(`starting child pid ${cp.pid}`)
                                cp.onexit = code => {
                                    cp.kill()
                                    //exec(`kill -9 ${cp.pid}`)
                                }
                                // process.addListener("SIGINT", cp.onexit)
                                // process.addListener("SIGTERM", cp.onexit)
                                process.addListener("exit", cp.onexit)
                                resolve(cp)
                            }

                            if ( prev_cp && prev_cp.connected ) {
                                const onexit = () => {
                                    console.log(`killing child pid ${prev_cp.pid}`)
                                    prev_cp.removeListener("exit", onexit)
                                    nextTick(() => start())
                                }

                                // process.removeListener("SIGTERM", prev_cp.onexit)
                                // process.removeListener("SIGINT", prev_cp.onexit)
                                process.removeListener("exit", prev_cp.onexit)
                                prev_cp.addListener("exit", onexit)
                                prev_cp.kill()
                                //exec(`kill -9 ${prev_cp.pid}`)
                            } else start()
                        })
                        const onchange = (change, filename) => {
                            if ( filename ) console.log(`[${main}] change detected in ${filename}`)
                            else console.log(`[${main}] change detected`)

                            queue = queue.then(start_child)
                            return queue
                        }

                        to_watch.forEach(path => {
                            const watcher = watch(path)
                            watcher.addListener("change", onchange)
                            watcher.addListener("error", e => console.error(e))
                        })
                        onchange()
                          .then(resolve)
                    }).catch(e => {
                        reject(e)
                    }))
                }

                const onend = () => {
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)
                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => new Promise((resolve, reject) => {
                const input = createReadStream( resolvePath(cwd, ".children.local") )
                const lines = createReadlineInterface({ input })

                let processing = Promise.resolve()
                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return

                    processing = processing.then(() => new Promise(resolve => {
                        const parts = line.split(/\s|\[|\]|,/gi)
                                          .filter(v => !!v)
                                          .map(v => joinPath(cwd ,v.trim()))
                        const main = parts.shift()
                        const to_watch = parts

                        let queue = Promise.resolve()
                        const start_child = prev_cp => new Promise(resolve => {
                            const start = () => {
                                const cp = fork(main, process.argv.slice(2), { env: process.env })
                                console.log(`starting child pid ${cp.pid}`)
                                cp.onexit = code => {
                                    cp.kill()
                                    //exec(`kill -9 ${cp.pid}`)
                                }
                                //process.addListener("SIGINT", cp.onexit)
                                //process.addListener("SIGTERM", cp.onexit)
                                process.addListener("exit", cp.onexit)
                                resolve(cp)
                            }

                            if ( prev_cp && prev_cp.connected ) {
                                const onexit = () => {
                                    console.log(`killing child pid ${prev_cp.pid}`)
                                    prev_cp.removeListener("exit", onexit)
                                    nextTick(() => start())
                                }

                                //process.removeListener("SIGINT", prev_cp.onexit)
                                //process.removeListener("SIGTERM", prev_cp.onexit)
                                process.removeListener("exit", prev_cp.onexit)
                                prev_cp.addListener("exit", onexit)
                                prev_cp.kill()
                                //exec(`kill -9 ${prev_cp.pid}`)
                            } else start()
                        })
                        const onchange = (change, filename) => {
                            if ( filename ) console.log(`[${main}] change detected in ${filename}`)
                            else console.log(`[${main}] change detected`)

                            queue = queue.then(start_child)
                            return queue
                        }

                        to_watch.forEach(path => {
                            const watcher = watch(path)
                            watcher.addListener("change", onchange)
                            watcher.addListener("error", e => console.error(e))
                        })
                        onchange()
                          .then(resolve)
                    }).catch(e => {
                        reject(e)
                    }))
                }

                const onend = () => {
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)
                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => this.dispatchEvent("ready"))
            .catch(e => {
                if ( e.warn )
                  this.dispatchEvent("warn", e)
                else
                  this.dispatchEvent("error", e)
            })
        }
      , socket_path: { enumerable: true,
            get: function(){
                return resolvePath(this.tmp_path, `./${this.uid}.sock`)
            }
        }
      , tmp_path: { enumerable: true,
            get: function(){
                return resolvePath(TMP, `./${this.uid}`)
            }
        }
      , uid: { enumerable: true,
            get: function(){
                return apps.get(this).get("uid")
            }
        }
      , unixSocketServer: { enumerable: true,
            get: function(){
                return apps.get(this).get("unixSocketServer")
            }
        }
    }
})
<commit_msg>add a parameter to dis/allow watchers<commit_after>"use strict"

const argv = new Set(process.argv.slice(2))
const NODE_ENV = [...argv].filter(v => v.indexOf("--NODE_ENV=") == 0 || v.indexOf("--env=") == 0)
                          .map(v => v.split("=")[1])[0] || null

const { exec, fork, spawn, spawnSync } = require("child_process")
const { createReadStream, createWriteStream, mkdir, watch } = require("fs")
const { dirname, resolve:resolvePath, join:joinPath } = require("path")
const { createInterface:createReadlineInterface } = require("readline")
const { inherits } = require("util")
const { nextTick } = process
const { typeOf } = require("ippankiban/lib/type")
const { networkInterfaces } = require("os")

const { class:klass } = require("ippankiban/lib/class")
const { Node } = require("ippankiban/lib/Node")
const { UID:{uid} } = require("ippankiban/lib/UID")
const { UnixSocketServer } = require("ippanserver/lib/UnixSocketServer")

const cwd = resolvePath(process.cwd(), dirname(process.mainModule.filename))

module.exports.App = klass(Node, statics => {
    const apps = new WeakMap
    const TMP = "/tmp" //TODO find alternatives on alternative OS

    const nl_comment_at_0 = char => char == "#" || char == ";"

    return {
        constructor: function({ watch:allow_watch=true }){
            Node.call(this)
            apps.set(this, new Map)
            apps.get(this).set("allow_watch", allow_watch)

            Promise.all([
                new Promise(resolve => {
                  const mandatories = new Set()

                  const input = createReadStream( resolvePath(cwd, "./.flags") )
                  const lines = createReadlineInterface({ input })

                  const online = line => {
                      line = line.trim()
                      if ( nl_comment_at_0(line[0]) || !line.length )
                        return

                      const parts = line.split(/\s/)
                                        .filter(v => !!v)

                      parts.forEach(part => mandatories.add(part))
                  }

                  const onend = () => {
                      input.removeListener("error", onerror)
                      lines.removeListener("error", onerror)
                      lines.removeListener("line", online)
                      lines.removeListener("close", onend)
                      resolve(mandatories)
                  }

                  const onerror = e => {
                      if ( e && e.code == "ENOENT" )
                        onend()
                      else {
                        throw e
                      }
                  }

                  input.addListener("error", onerror)
                  lines.addListener("error", onerror)
                  lines.addListener("line", online)
                  lines.addListener("close", onend)
                }).then(mandatories => new Promise(resolve => {
                      const execArgv = new Set(process.execArgv)
                      let ok = true

                      mandatories.forEach(arg => {
                          if ( execArgv.has(arg) )
                            return
                          ok = false
                          execArgv.add(arg)
                      })

                      if ( ok )
                        return resolve()

                      const cp = fork(resolvePath(cwd, process.mainModule.filename), process.argv.slice(2), {execArgv: [...execArgv]})

                      process.addListener("SIGINT", () => cp.kill())
                      process.addListener("SIGTERM", () => cp.kill())
                      process.addListener("exit", () => cp.kill())
                      cp.addListener("SIGINT", () => process.exit())
                      cp.addListener("SIGTERM", () => process.exit())
                      cp.addListener("exit", () => process.exit())

                      const e = new Error("missing mandatory parameters, restarting with child process")
                      e.warn = true
                      throw e
                  }))
            ])
            .then(() => new Promise(resolve => {
                //process.setMaxListeners(10)
                process.on("SIGINT", e => process.exit())
                process.on("SIGTERM", e => process.exit())

                resolve()
            }))
            .then(() => new Promise(resolve => {
                /*
                    project environment configuration
                */
                const input = createReadStream( resolvePath(cwd, "./.env") )
                const lines = createReadlineInterface({ input })
                const variables = new Map
                const pairs = new Map

                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return
                    else if ( line[0] == "$" ) {
                        const idx = line.indexOf("=")
                        variables.set(line.slice(1, idx).trim(), line.slice(idx+1).trim())
                    }
                    else {
                        const idx = line.indexOf("=")
                        const key = line.slice(0, idx).trim()
                        let value = line.slice(idx+1).trim()

                        if ( value[0] == "[" && value[value.length-1] == "]")
                            value = value.slice(1, -1)
                                         .split(",")
                                         .map(v => v.trim())

                        pairs.set(key, value)
                    }
                }

                const onend = () => {
                    input.removeListener("error", onerror)
                    lines.removeListener("error", onerror)
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)

                    pairs.forEach((value, key) => {
                        variables.forEach((replace, match) => {
                            if ( typeOf(value) == "string" )
                              value = value.replace(new RegExp(`\\$${match}`, "g"), replace)
                            else if ( typeOf(value) == "array" )
                              value = value.map(v => v.replace(new RegExp(`\\$${match}`, "g"), replace))
                        })

                        process.env[key.toUpperCase()] = value
                    })

                    variables.clear()
                    pairs.clear()
                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => new Promise(resolve => {
                /*
                    user config, can be present or not
                */
                const input = createReadStream( resolvePath(cwd, "./.local") )
                const lines = createReadlineInterface({ input })
                const variables = new Map
                const pairs = new Map

                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return
                    else if ( line[0] == "$" ) {
                        const idx = line.indexOf("=")
                        variables.set(line.slice(1, idx).trim(), line.slice(idx+1).trim())
                    }
                    else {
                        const idx = line.indexOf("=")
                        const key = line.slice(0, idx).trim()
                        let value = line.slice(idx+1).trim()

                        if ( value[0] == "[" && value[value.length-1] == "]")
                            value = value.slice(1, -1)
                                         .split(",")
                                         .map(v => v.trim())

                        pairs.set(key, value)
                    }
                }

                const onend = () => {
                    input.removeListener("error", onerror)
                    lines.removeListener("error", onerror)
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)

                    pairs.forEach((value, key) => {
                        variables.forEach((replace, match) => {
                            if ( typeOf(value) == "string" )
                              value = value.replace(new RegExp(`\\$${match}`, "g"), replace)
                            else if ( typeOf(value) == "array" )
                              value = value.map(v => v.replace(new RegExp(`\\$${match}`, "g"), replace))
                        })

                        process.env[key.toUpperCase()] = value
                    })

                    variables.clear()
                    pairs.clear()

                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => new Promise(resolve => {
                apps.get(this).set("uid", uid())

                resolve()
            }))
            .then(() => new Promise(resolve => {
                const mode = 0O0775 & (~process.umask())
                const ifaces = networkInterfaces()

                process.env.NODE_ENV = process.env.NODE_ENV  || NODE_ENV || "dev"
                process.env.ROOT = cwd
                process.env.TMP = this.tmp_path
                process.env.APP_SOCKET = this.socket_path

                Object.keys(ifaces).forEach(ifname => {
                    ifaces[ifname].forEach(({address, family}) => {
                        if ( family == "IPv4" && address !== "127.0.0.1" )
                          process.env.IPV4 = address
                    })
                })

                mkdir(process.env.TMP, mode, err => {
                    if ( err ) throw err

                    process.addListener("SIGINT", () => process.exit())
                    process.addListener("SIGTERM", () => process.exit())
                    process.addListener("exit", code => {
                        console.log(`[ippanAppManager] attempting to delete ${process.env.TMP}`)
                        spawnSync("rm", ["-rf", process.env.TMP])
                    })

                    resolve()
                })
            }))
            .then(() => new Promise(resolve => {
                apps.get(this).set("unixSocketServer", new UnixSocketServer(this.socket_path))
                this.appendChild(this.unixSocketServer)

                this.unixSocketServer.addEventListener("listening", e => {
                    console.log(`app socket listening (${this.unixSocketServer.socket})`)
                    this.dispatchEvent("unixsocketserver")
                    resolve()
                })
            }))
            .then(() => new Promise((resolve, reject) => {
                const input = createReadStream( resolvePath(cwd, ".children") )
                const lines = createReadlineInterface({ input })

                let processing = Promise.resolve()
                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return

                    processing = processing.then(() => new Promise(resolve => {
                        const parts = line.split(/\s|\[|\]|,/gi)
                                          .filter(v => !!v)
                                          .map(v => joinPath(cwd ,v.trim()))
                        const main = parts.shift()
                        const to_watch = parts

                        let queue = Promise.resolve()
                        const start_child = prev_cp => new Promise(resolve => {
                            const start = () => {
                                const cp = fork(main, process.argv.slice(2), { env: process.env })
                                console.log(`starting child pid ${cp.pid}`)
                                cp.onexit = code => {
                                    cp.kill()
                                    //exec(`kill -9 ${cp.pid}`)
                                }
                                // process.addListener("SIGINT", cp.onexit)
                                // process.addListener("SIGTERM", cp.onexit)
                                process.addListener("exit", cp.onexit)
                                resolve(cp)
                            }

                            if ( prev_cp && prev_cp.connected ) {
                                const onexit = () => {
                                    console.log(`killing child pid ${prev_cp.pid}`)
                                    prev_cp.removeListener("exit", onexit)
                                    nextTick(() => start())
                                }

                                // process.removeListener("SIGTERM", prev_cp.onexit)
                                // process.removeListener("SIGINT", prev_cp.onexit)
                                process.removeListener("exit", prev_cp.onexit)
                                prev_cp.addListener("exit", onexit)
                                prev_cp.kill()
                                //exec(`kill -9 ${prev_cp.pid}`)
                            } else start()
                        })
                        const onchange = (change, filename) => {
                            if ( filename ) console.log(`[${main}] change detected in ${filename}`)
                            else console.log(`[${main}] change detected`)

                            queue = queue.then(start_child)
                            return queue
                        }

                        if ( this.allow_watch )
                          to_watch.forEach(path => {
                              const watcher = watch(path)
                              watcher.addListener("change", onchange)
                              watcher.addListener("error", e => console.error(e))
                          })
                        onchange()
                          .then(resolve)
                    }).catch(e => {
                        reject(e)
                    }))
                }

                const onend = () => {
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)
                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => new Promise((resolve, reject) => {
                const input = createReadStream( resolvePath(cwd, ".children.local") )
                const lines = createReadlineInterface({ input })

                let processing = Promise.resolve()
                const online = line => {
                    line = line.trim()
                    if ( nl_comment_at_0(line[0]) || !line.length )
                      return

                    processing = processing.then(() => new Promise(resolve => {
                        const parts = line.split(/\s|\[|\]|,/gi)
                                          .filter(v => !!v)
                                          .map(v => joinPath(cwd ,v.trim()))
                        const main = parts.shift()
                        const to_watch = parts

                        let queue = Promise.resolve()
                        const start_child = prev_cp => new Promise(resolve => {
                            const start = () => {
                                const cp = fork(main, process.argv.slice(2), { env: process.env })
                                console.log(`starting child pid ${cp.pid}`)
                                cp.onexit = code => {
                                    cp.kill()
                                    //exec(`kill -9 ${cp.pid}`)
                                }
                                //process.addListener("SIGINT", cp.onexit)
                                //process.addListener("SIGTERM", cp.onexit)
                                process.addListener("exit", cp.onexit)
                                resolve(cp)
                            }

                            if ( prev_cp && prev_cp.connected ) {
                                const onexit = () => {
                                    console.log(`killing child pid ${prev_cp.pid}`)
                                    prev_cp.removeListener("exit", onexit)
                                    nextTick(() => start())
                                }

                                //process.removeListener("SIGINT", prev_cp.onexit)
                                //process.removeListener("SIGTERM", prev_cp.onexit)
                                process.removeListener("exit", prev_cp.onexit)
                                prev_cp.addListener("exit", onexit)
                                prev_cp.kill()
                                //exec(`kill -9 ${prev_cp.pid}`)
                            } else start()
                        })
                        const onchange = (change, filename) => {
                            if ( filename ) console.log(`[${main}] change detected in ${filename}`)
                            else console.log(`[${main}] change detected`)

                            queue = queue.then(start_child)
                            return queue
                        }

                        if ( !this.allow_watch )
                          to_watch.forEach(path => {
                              const watcher = watch(path)
                              watcher.addListener("change", onchange)
                              watcher.addListener("error", e => console.error(e))
                          })
                        onchange()
                          .then(resolve)
                    }).catch(e => {
                        reject(e)
                    }))
                }

                const onend = () => {
                    lines.removeListener("line", online)
                    lines.removeListener("close", onend)
                    resolve()
                }

                const onerror = e => {
                    if ( e && e.code == "ENOENT" )
                      onend()
                    else {
                      throw e
                    }
                }

                input.addListener("error", onerror)
                lines.addListener("line", online)
                lines.addListener("close", onend)
            }))
            .then(() => this.dispatchEvent("ready"))
            .catch(e => {
                if ( e.warn )
                  this.dispatchEvent("warn", e)
                else
                  this.dispatchEvent("error", e)
            })
        }
      , socket_path: { enumerable: true,
            get: function(){
                return resolvePath(this.tmp_path, `./${this.uid}.sock`)
            }
        }
      , tmp_path: { enumerable: true,
            get: function(){
                return resolvePath(TMP, `./${this.uid}`)
            }
        }
      , uid: { enumerable: true,
            get: function(){
                return apps.get(this).get("uid")
            }
        }
      , unixSocketServer: { enumerable: true,
            get: function(){
                return apps.get(this).get("unixSocketServer")
            }
        }
      , allow_watch: { enumerable: true,
            get: function(){
                return apps.get(this).get("allow_watch")
            }
        }
    }
})
<|endoftext|>


<commit_before>'use strict';
var path = require('path')
  , glob = require('glob')
  , ObjectManage = require('object-manage')
  , async = require('async')
  , events = require('events')
  , util = require('util')
  , emitter

/**
 * Setup Event Emitter
 * @type {EventEmitter}
 */
module.exports = emitter = new events.EventEmitter()

/**
 * Main constructor sets options
 * @param options
 */
var Apx = function(options){
  var self = this
  self.sysLog = require('./SysLog')
  //set syslog level now
  if('production' === process.env.NODE_ENV){
    self.sysLog.level = 1
  }
  if(options.sysLogLevel) self.sysLog.level = options.sysLogLevel
  self.sysLog.info('Welcome to APX')
  //freshen
  self.initializers = []
  self.log = null
  self.middleware = []
  this.models = []
  self.services = []
  self.tasks = []
  self.translators = []
  self.options = options
}

/**
 * Options passed to the constructor
 * @type {{}}
 */
Apx.prototype.options = {}

/**
 * Ready state of APX
 *  0 - Dead
 *  1 - Ready
 *  2 - Configured
 *  3 - Starting
 *  4 - Stopping
 * @type {Number}
 */
Apx.prototype.readyState = 0

/**
 * Registered initializers
 * @type {Array}
 */
Apx.prototype.initializers = []

/**
 * Winston logging instance
 * @type {null}
 */
Apx.prototype.log = null

/**
 * Instantiated services
 * @type {Array}
 */
Apx.prototype.services = []

/**
 * Registered tasks
 * @type {Array}
 */
Apx.prototype.tasks = []

/**
 * Registered translators
 * @type {Array}
 */
Apx.prototype.translators = []

/**
 * Request object
 * @type {Function}
 */
Apx.prototype.Request = require('./Request')

/**
 * Response Object
 * @type {Function}
 */
Apx.prototype.Response = require('./Response')

/**
 * Initial config schema
 * @type {*}
 */
Apx.prototype.configSchema = {
  testing: false,
  cwd: '',
  middleware: [],
  models: [],
  tasks: [],
  translators: [],
  initializers: []
}

/**
 * Load additional config properties
 */
Apx.prototype.load = function(options){
  this.config.load(options)
}

/**
 * Worker init
 */
Apx.prototype.start = function(){
  var self = this
  //send welcome message
  self.sysLog.info('Hi! Beginning APX initialization and startup')
  //setup loaders so we can run them async (which is async of async)
  var loaders = [
    //load configuration
    function(finish){
      self.sysLog.info('Loading configuration options')
      self.options = self.options || {}
      self.config = new ObjectManage(self.configSchema)
      //load runtime config first and last (config files in the middle)
      self.config.load(self.options)
      //load additional config
      self.loadItems(
        self.config.get('config'),
        function(config,next){
          self.config.load(config)
          next()
        },
        //finish init here so we know the config is loaded fully
        function(err){
          if(err) throw err
          //pass rest of the options to config
          //  so that runtime options are kept over all else
          self.config.load(self.options)
          self.sysLog.info('Config loading complete')
          //update ready state
          self.readyState = 2
          emitter.emit('readyStateChange',self.readyState)
          //update ready state
          self.readyState = 3
          emitter.emit('readyStateChange',self.readyState)
          finish()
        }
      )
    },
    //load initializers
    function(finish){
      if(self.config.get('initializers')){
        self.sysLog.info('Starting to load initializers')
        self.loadItems(
          self.config.get('initializers'),
          function(initializer,next){
            self.sysLog.info('Starting initializer: ' + initializer.name)
            self.initializers[initializer.name] = initializer.start(self,next)
          },
          function(err){
            self.sysLog.info('Finished loading initializers')
            finish(err)
          }
        )
      }

    },
    //load translators
    function(finish){
      if(self.config.get('translators')){
        self.sysLog.info('Starting to load translators')
        self.loadItems(
          self.config.get('translators'),
          function(translator,next){
            self.sysLog.info('Starting translator: ' + translator.name)
            self.translators[translator.name] = translator.start(self,next)
          },
          function(err){
            self.sysLog.info('Finished loading translators')
            finish(err)
          }
        )
      }

    }
  ]
  //fire the loaders
  async.eachSeries(
    loaders,
    function(loader,next){
      loader(next)
    },
    function(err){
      //emit error event if there are errors
      if(err){
        emitter.emit('error',err)
      }
      //call the ready function
      self.sysLog.info('APX system startup complete')
      //update ready state
      self.readyState = 1
      emitter.emit('readyStateChange',self.readyState)
      //fire ready event
      emitter.emit('ready',self)
    }
  )

}

/**
 * Destruct the APX instance and shutdown translators
 */
Apx.prototype.stop = function(){
  var self = this
  //update ready state
  self.readyState = 4
  emitter.emit('readyStateChange',self.readyState)
  //inform user of shutdown
  self.sysLog.info('Starting to shutdown APX')
  //setup loaders so we can run them async (which is async of async)
  var loaders = [
    //load translators
    function(finish){
      if(self.config.get('translators')){
        self.sysLog.info('Starting to shutdown translators')
        self.loadItems(
          self.config.get('translators'),
          function(translator,next){
            self.sysLog.info('Stopping translator: ' + translator.name)
            self.translators[translator.name] = translator.stop(self,next)
          },
          function(err){
            self.sysLog.info('Finished shutting down translators')
            finish(err)
          }
        )
      }
    },
    //load initializers
    function(finish){
      if(self.config.get('initializers')){
        self.sysLog.info('Starting to shutdown initializers')
        self.loadItems(
          self.config.get('initializers'),
          function(initializer,next){
            self.sysLog.info('Stopping initializer: ' + initializer.name)
            self.initializers[initializer.name] = initializer.stop(self,next)
          },
          function(err){
            self.sysLog.info('Finished shutting down initializers')
            finish(err)
          }
        )
      }
    }
  ]
  //fire the loaders
  async.eachSeries(
    loaders,
    function(loader,next){
      loader(next)
    },
    function(err){
      //emit error event if there are errors
      if(err){
        emitter.emit('error',err)
      }
      //call the ready function
      self.sysLog.info('APX system shutdown complete')
      //update ready stats
      self.readyState = 0
      emitter.emit('readyStateChange',self.readyState)
      //fire dead event
      emitter.emit('dead',self)
    }
  )
}

/**
 * Resolve path with CWD
 * @param file
 * @returns {*}
 */
Apx.prototype.resolvePath = function(file){
  var rv = file
    , cwd = this.config.get('cwd')
  if(cwd)
    rv = cwd + '/' + file
  return path.resolve(rv)
}

/**
 * Will take an array of items/globs and
 * load them against the environment and will
 * fire iterator with the item object on success
 * @param items
 * @param iterator
 * @param done
 */
Apx.prototype.loadItems = function(items,iterator,done){
  if(undefined === items) done()
  else {
    var self = this
      , cwd = path.resolve(self.config.get('cwd'))
    if('string' === typeof items) items = [items]
    if('object' === typeof items && !util.isArray(items)) items = [items]
    //do all of this async
    async.each(
      items,
      function(item,finish){
        //objects should be passed directly
        if('object' === typeof item){
          iterator(item,finish)
        /**
         * Try to load a package name natively
         *  this will check if the package is a file or should be loaded using the
         *  methods of the nodejs require() function see the documentation
         *  here: http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders
         */
        } else if(
          0 !== item.indexOf('./') &&
          0 !== item.indexOf('../') &&
          0 !== item.indexOf('/') &&
          (item.length - 4) === item.indexOf('.js') &&
          (item.length - 6) === item.indexOf('.json') &&
          (item.length - 6) === item.indexOf('.node')
        ){
          self.sysLog.info('Loading package (' + item + ')')
          iterator(require(item),finish)
          //try to resolve the path with glob
        } else {
          glob(item,{cwd: cwd},function(err,files){
            if(err) throw err
            async.each(
              files,
              function(file,next){
                self.sysLog.info('Loading package from path (' + self.resolvePath(file) + ')')
                iterator(require(self.resolvePath(file)),next)
              },
              finish
            )
          })
        }
      },
      done
    )
  }
}

/**
 * Run APX action
 *  Should be called by a translator
 * @param action  Can be an action or a filename to require
 * @param req
 * @param method
 * @param fn
 */
Apx.prototype.runAction = function(action,req,method,fn){
  var self = this
  if('function' === typeof method){
    fn = method
    method = 'run'
  }
  if('string' === typeof action) action = require(self.resolvePath(action))
  if(false === (req instanceof self.Request))
    req = new self.Request(req)
  var res = new self.Response()
  var runMiddleware = function(position,done){
    //make sure position is usable even if its not passed
    if('function' === typeof position){
      done = position
      position = 'pre'
    }
    if('pre' !== position && 'post' !== position){
      position = 'pre'
    }
    //run middleware
    if(self.config.exists('middleware')){
      self.sysLog.info('Running ' + position + ' middleware for action ' + action.name + ':' + method)
      self.loadItems(
        self.config.get('middleware'),
        function(middleware,next){
          var method = position
          if('pre' === position && 'function' === typeof middleware.run){
            method = 'run'
          }
          if('post' === position){
            method = 'post'
          }
          //only run the middleware if a method is defined
          if('function' === typeof middleware[method]){
            middleware[method](self,req,res,next,done)
          }

        },
        done
      )
    } else {
      done(null)
    }
  }
  var runAction = function(next){
    self.sysLog.info('Running action ' + action.name + ' : ' + method)
    emitter.emit('runActionBefore',action)
    action[method](self,req,res,function(err){
      emitter.emit('runActionAfter',action,err,res)
      next(err,res)
    })
  }
  //run the action and middleware before and after
  runMiddleware('pre',function(err){
    if(err){
      emitter.emit('error',err)
    } else {
      runAction(function(err,res){
        if(err){
          emitter.emit('error',err)
        } else {
          runMiddleware('post',function(err){
            if(err){
              emitter.emit('error',err)
            } else {
              fn(err,res)
            }
          })
        }
      })
    }
  })
}

/**
 * Run APX task
 * @param task  Can be a task or a filename to require
 * @param req
 * @param done
 */
Apx.prototype.runTask = function(task,req,done){
  if('string' === typeof task) task = require(this.resolvePath(task))
  if(false === (req instanceof this.Request))
    req = new this.Request(req)
  this.sysLog.info('Running task ' + task.name)
  //run task
  emitter.emit('runTaskBefore',task)
  task.run(this,req,function(err){
    emitter.emit('runTaskAfter',task)
    done(err)
  })

}

/**
 * Returns new service instance
 * @param service  Can be a service or a filename to require
 * @returns {service}
 */
Apx.prototype.newService = function(service){
  if('string' === typeof service) service = require(this.resolvePath(service))
  this.sysLog.info('Returning new service instance for ' + service.name)
  return new service.module()
}

/**
 * Obtain service instance
 *  manages singletons
 * @param service  Can be a service or a filename to require
 * @returns {*}
 */
Apx.prototype.service = function(service){
  if('string' === typeof service) service = require(this.resolvePath(service))
  if(!this.services[service.name]){
    this.services[service.name] = new service.module()
  }
  this.sysLog.info('Returning service instance for ' + service.name)
  return this.services[service.name]
}

/**
 * Operation of APX library
 *   Implements standard daemon operators (start/stop)
 */
module.exports.instance = null
module.exports.setup = function(opts){
  module.exports.instance = new Apx(opts)
}
module.exports.start = function(opts){
  if(opts){
    module.exports.setup(opts)
  }
  if(!module.exports.instance) throw new Error('Tried to start APX without setting it up')
  module.exports.instance.start()
}
module.exports.stop = function(){
  module.exports.instance.stop()
  emitter.on('dead',function(){
    module.exports.instance = null
  })
}
module.exports.restart = function(){
  module.exports.stop()
  module.exports.start()
}
module.exports.Apx = Apx
<commit_msg>better checking for passing direct objects to loadItems<commit_after>'use strict';
var path = require('path')
  , glob = require('glob')
  , ObjectManage = require('object-manage')
  , async = require('async')
  , events = require('events')
  , util = require('util')
  , emitter

/**
 * Setup Event Emitter
 * @type {EventEmitter}
 */
module.exports = emitter = new events.EventEmitter()

/**
 * Main constructor sets options
 * @param options
 */
var Apx = function(options){
  var self = this
  self.sysLog = require('./SysLog')
  //set syslog level now
  if('production' === process.env.NODE_ENV){
    self.sysLog.level = 1
  }
  if(options.sysLogLevel) self.sysLog.level = options.sysLogLevel
  self.sysLog.info('Welcome to APX')
  //freshen
  self.initializers = []
  self.log = null
  self.middleware = []
  this.models = []
  self.services = []
  self.tasks = []
  self.translators = []
  self.options = options
}

/**
 * Options passed to the constructor
 * @type {{}}
 */
Apx.prototype.options = {}

/**
 * Ready state of APX
 *  0 - Dead
 *  1 - Ready
 *  2 - Configured
 *  3 - Starting
 *  4 - Stopping
 * @type {Number}
 */
Apx.prototype.readyState = 0

/**
 * Registered initializers
 * @type {Array}
 */
Apx.prototype.initializers = []

/**
 * Winston logging instance
 * @type {null}
 */
Apx.prototype.log = null

/**
 * Instantiated services
 * @type {Array}
 */
Apx.prototype.services = []

/**
 * Registered tasks
 * @type {Array}
 */
Apx.prototype.tasks = []

/**
 * Registered translators
 * @type {Array}
 */
Apx.prototype.translators = []

/**
 * Request object
 * @type {Function}
 */
Apx.prototype.Request = require('./Request')

/**
 * Response Object
 * @type {Function}
 */
Apx.prototype.Response = require('./Response')

/**
 * Initial config schema
 * @type {*}
 */
Apx.prototype.configSchema = {
  testing: false,
  cwd: '',
  middleware: [],
  models: [],
  tasks: [],
  translators: [],
  initializers: []
}

/**
 * Load additional config properties
 */
Apx.prototype.load = function(options){
  this.config.load(options)
}

/**
 * Worker init
 */
Apx.prototype.start = function(){
  var self = this
  //send welcome message
  self.sysLog.info('Hi! Beginning APX initialization and startup')
  //setup loaders so we can run them async (which is async of async)
  var loaders = [
    //load configuration
    function(finish){
      self.sysLog.info('Loading configuration options')
      self.options = self.options || {}
      self.config = new ObjectManage(self.configSchema)
      //load runtime config first and last (config files in the middle)
      self.config.load(self.options)
      //load additional config
      self.loadItems(
        self.config.get('config'),
        function(config,next){
          self.config.load(config)
          next()
        },
        //finish init here so we know the config is loaded fully
        function(err){
          if(err) throw err
          //pass rest of the options to config
          //  so that runtime options are kept over all else
          self.config.load(self.options)
          self.sysLog.info('Config loading complete')
          //update ready state
          self.readyState = 2
          emitter.emit('readyStateChange',self.readyState)
          //update ready state
          self.readyState = 3
          emitter.emit('readyStateChange',self.readyState)
          finish()
        }
      )
    },
    //load initializers
    function(finish){
      if(self.config.get('initializers')){
        self.sysLog.info('Starting to load initializers')
        self.loadItems(
          self.config.get('initializers'),
          function(initializer,next){
            self.sysLog.info('Starting initializer: ' + initializer.name)
            self.initializers[initializer.name] = initializer.start(self,next)
          },
          function(err){
            self.sysLog.info('Finished loading initializers')
            finish(err)
          }
        )
      }

    },
    //load translators
    function(finish){
      if(self.config.get('translators')){
        self.sysLog.info('Starting to load translators')
        self.loadItems(
          self.config.get('translators'),
          function(translator,next){
            self.sysLog.info('Starting translator: ' + translator.name)
            self.translators[translator.name] = translator.start(self,next)
          },
          function(err){
            self.sysLog.info('Finished loading translators')
            finish(err)
          }
        )
      }

    }
  ]
  //fire the loaders
  async.eachSeries(
    loaders,
    function(loader,next){
      loader(next)
    },
    function(err){
      //emit error event if there are errors
      if(err){
        emitter.emit('error',err)
      }
      //call the ready function
      self.sysLog.info('APX system startup complete')
      //update ready state
      self.readyState = 1
      emitter.emit('readyStateChange',self.readyState)
      //fire ready event
      emitter.emit('ready',self)
    }
  )

}

/**
 * Destruct the APX instance and shutdown translators
 */
Apx.prototype.stop = function(){
  var self = this
  //update ready state
  self.readyState = 4
  emitter.emit('readyStateChange',self.readyState)
  //inform user of shutdown
  self.sysLog.info('Starting to shutdown APX')
  //setup loaders so we can run them async (which is async of async)
  var loaders = [
    //load translators
    function(finish){
      if(self.config.get('translators')){
        self.sysLog.info('Starting to shutdown translators')
        self.loadItems(
          self.config.get('translators'),
          function(translator,next){
            self.sysLog.info('Stopping translator: ' + translator.name)
            self.translators[translator.name] = translator.stop(self,next)
          },
          function(err){
            self.sysLog.info('Finished shutting down translators')
            finish(err)
          }
        )
      }
    },
    //load initializers
    function(finish){
      if(self.config.get('initializers')){
        self.sysLog.info('Starting to shutdown initializers')
        self.loadItems(
          self.config.get('initializers'),
          function(initializer,next){
            self.sysLog.info('Stopping initializer: ' + initializer.name)
            self.initializers[initializer.name] = initializer.stop(self,next)
          },
          function(err){
            self.sysLog.info('Finished shutting down initializers')
            finish(err)
          }
        )
      }
    }
  ]
  //fire the loaders
  async.eachSeries(
    loaders,
    function(loader,next){
      loader(next)
    },
    function(err){
      //emit error event if there are errors
      if(err){
        emitter.emit('error',err)
      }
      //call the ready function
      self.sysLog.info('APX system shutdown complete')
      //update ready stats
      self.readyState = 0
      emitter.emit('readyStateChange',self.readyState)
      //fire dead event
      emitter.emit('dead',self)
    }
  )
}

/**
 * Resolve path with CWD
 * @param file
 * @returns {*}
 */
Apx.prototype.resolvePath = function(file){
  var rv = file
    , cwd = this.config.get('cwd')
  if(cwd)
    rv = cwd + '/' + file
  return path.resolve(rv)
}

/**
 * Will take an array of items/globs and
 * load them against the environment and will
 * fire iterator with the item object on success
 * @param items
 * @param iterator
 * @param done
 */
Apx.prototype.loadItems = function(items,iterator,done){
  if(undefined === items) done()
  else {
    var self = this
      , cwd = path.resolve(self.config.get('cwd'))
    if('string' === typeof items) items = [items]
    if('object' === typeof items && !util.isArray(items)) items = [items]
    //do all of this async
    async.each(
      items,
      function(item,finish){
        //objects should be passed directly
        if('object' === typeof item && !util.isArray(item) && 'string' !== typeof item){
          iterator(item,finish)
        /**
         * Try to load a package name natively
         *  this will check if the package is a file or should be loaded using the
         *  methods of the nodejs require() function see the documentation
         *  here: http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders
         */
        } else if(
          0 !== item.indexOf('./') &&
          0 !== item.indexOf('../') &&
          0 !== item.indexOf('/') &&
          (item.length - 4) === item.indexOf('.js') &&
          (item.length - 6) === item.indexOf('.json') &&
          (item.length - 6) === item.indexOf('.node')
        ){
          self.sysLog.info('Loading package (' + item + ')')
          iterator(require(item),finish)
          //try to resolve the path with glob
        } else {
          glob(item,{cwd: cwd},function(err,files){
            if(err) throw err
            async.each(
              files,
              function(file,next){
                self.sysLog.info('Loading package from path (' + self.resolvePath(file) + ')')
                iterator(require(self.resolvePath(file)),next)
              },
              finish
            )
          })
        }
      },
      done
    )
  }
}

/**
 * Run APX action
 *  Should be called by a translator
 * @param action  Can be an action or a filename to require
 * @param req
 * @param method
 * @param fn
 */
Apx.prototype.runAction = function(action,req,method,fn){
  var self = this
  if('function' === typeof method){
    fn = method
    method = 'run'
  }
  if('string' === typeof action) action = require(self.resolvePath(action))
  if(false === (req instanceof self.Request))
    req = new self.Request(req)
  var res = new self.Response()
  var runMiddleware = function(position,done){
    //make sure position is usable even if its not passed
    if('function' === typeof position){
      done = position
      position = 'pre'
    }
    if('pre' !== position && 'post' !== position){
      position = 'pre'
    }
    //run middleware
    if(self.config.exists('middleware')){
      self.sysLog.info('Running ' + position + ' middleware for action ' + action.name + ':' + method)
      self.loadItems(
        self.config.get('middleware'),
        function(middleware,next){
          var method = position
          if('pre' === position && 'function' === typeof middleware.run){
            method = 'run'
          }
          if('post' === position){
            method = 'post'
          }
          //only run the middleware if a method is defined
          if('function' === typeof middleware[method]){
            middleware[method](self,req,res,next,done)
          }

        },
        done
      )
    } else {
      done(null)
    }
  }
  var runAction = function(next){
    self.sysLog.info('Running action ' + action.name + ' : ' + method)
    emitter.emit('runActionBefore',action)
    action[method](self,req,res,function(err){
      emitter.emit('runActionAfter',action,err,res)
      next(err,res)
    })
  }
  //run the action and middleware before and after
  runMiddleware('pre',function(err){
    if(err){
      emitter.emit('error',err)
    } else {
      runAction(function(err,res){
        if(err){
          emitter.emit('error',err)
        } else {
          runMiddleware('post',function(err){
            if(err){
              emitter.emit('error',err)
            } else {
              fn(err,res)
            }
          })
        }
      })
    }
  })
}

/**
 * Run APX task
 * @param task  Can be a task or a filename to require
 * @param req
 * @param done
 */
Apx.prototype.runTask = function(task,req,done){
  if('string' === typeof task) task = require(this.resolvePath(task))
  if(false === (req instanceof this.Request))
    req = new this.Request(req)
  this.sysLog.info('Running task ' + task.name)
  //run task
  emitter.emit('runTaskBefore',task)
  task.run(this,req,function(err){
    emitter.emit('runTaskAfter',task)
    done(err)
  })

}

/**
 * Returns new service instance
 * @param service  Can be a service or a filename to require
 * @returns {service}
 */
Apx.prototype.newService = function(service){
  if('string' === typeof service) service = require(this.resolvePath(service))
  this.sysLog.info('Returning new service instance for ' + service.name)
  return new service.module()
}

/**
 * Obtain service instance
 *  manages singletons
 * @param service  Can be a service or a filename to require
 * @returns {*}
 */
Apx.prototype.service = function(service){
  if('string' === typeof service) service = require(this.resolvePath(service))
  if(!this.services[service.name]){
    this.services[service.name] = new service.module()
  }
  this.sysLog.info('Returning service instance for ' + service.name)
  return this.services[service.name]
}

/**
 * Operation of APX library
 *   Implements standard daemon operators (start/stop)
 */
module.exports.instance = null
module.exports.setup = function(opts){
  module.exports.instance = new Apx(opts)
}
module.exports.start = function(opts){
  if(opts){
    module.exports.setup(opts)
  }
  if(!module.exports.instance) throw new Error('Tried to start APX without setting it up')
  module.exports.instance.start()
}
module.exports.stop = function(){
  module.exports.instance.stop()
  emitter.on('dead',function(){
    module.exports.instance = null
  })
}
module.exports.restart = function(){
  module.exports.stop()
  module.exports.start()
}
module.exports.Apx = Apx
<|endoftext|>


<commit_before>/**
 * @module Arr
 * @private
 * @mixin
 * @description Exports Arr class.
 */

import Super from './Super';
import constructors from './constants/constructors';
import {
  isArrayLike, isNaN, isNumber, isString,
  toArray, validate, iterate, Symbol, defineProperties,
  inherits, checkClassInstance, possibleSuperClassReturn
} from './helpers';

/**
 * @typedef {Array|*} ArrayLike
 * @public
 * @description Array-like type.
 */

/**
 * @callback ArrayCallback
 * @public
 * @param {Number} i - Iteration index.
 */

/**
 * @callback CompareFunction
 * @public
 * @param {*} x - First value to be compared.
 * @param {*} y - Second value to be compared.
 */

/**
 * @class Arr
 * @extends Super
 * @public
 * @param {Array} [array = []] - An array to wrap.
 * @returns {Arr} Instance of Arr.
 * @description Wrap of an array.
 *
 * @example
 * new Arr([1, 2]);
 */
export function Arr(array = []) {
  checkClassInstance(this, Arr, 'Arr');

  const $this = possibleSuperClassReturn(this, Super, [toArray(array)]);

  Object.defineProperty($this, '$$', { value: array });

  return $this;

  /**
   * @member {Array} Arr#$
   * @public
   * @description Made array.
   */

  /**
   * @member {ArrayLike} Arr#$$
   * @public
   * @description Original array.
   */
}

inherits(Arr, Super);

defineProperties(Arr.prototype, {
  /**
   * @member {Function} Arr#concat
   * @public
   * @param {...(Array|*)} arrays - Arrays or any other values to concat the array with.
   * @returns {Arr} New instance of Arr.
   * @description Synonym for
   * [Array#concat]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/concat}.
   */
  concat() {
    const array = toArray(this.$, true);

    iterate(arguments, (value) => {
      value = new Super(value).$;

      if (isArrayLike(value) && !isString(value)) {
        iterate(value, (value) => {
          array.push(value);
        });

        return;
      }

      array.push(value);
    });

    return new this.constructor(array);
  },

  /**
   * @member {Function} Arr#indexOf
   * @public
   * @param {*} value - Value to search.
   * @returns {Number} Index of the argument.
   * @description Almost the same as {@link Super#keyOf}. The difference is that if the value is not found
   * -1 returned instead of null and if found Number(key) returned.
   *
   * @example
   * new Arr([1, 2, 3]).indexOf(1);       // 0
   * new Arr([1, 2, 3]).indexOf('1');     // 0
   * new Arr([1, 2, 3]).indexOf(3);       // -1
   * new Arr([1, 2, NaN]).indexOf(NaN);   // 2
   */
  indexOf(value) {
    const key = this.keyOf(value);

    return key === null ? -1 : Number(key);
  },

  /**
   * @member {Function} Arr#indexOfStrict
   * @public
   * @param {*} value - Value to search.
   * @returns {Number} Index of the argument.
   * @description Almost the same as {@link Super#keyOfStrict}. The difference is that if the value is not found
   * -1 returned instead of null and if found Number(key) returned.
   *
   * @example
   * new Arr([1, 2, 3]).indexOfStrict(1);       // 0
   * new Arr([1, 2, 3]).indexOfStrict('1');     // -1
   * new Arr([1, 2, 3]).indexOfStrict(3);       // -1
   * new Arr([1, 2, NaN]).indexOfStrict(NaN);   // 2
   */
  indexOfStrict(value) {
    const key = this.keyOfStrict(value);

    return key === null ? -1 : Number(key);
  },

  /**
   * @member {Function} Arr#join
   * @public
   * @param {String} [separator = ','] - String that is passed to Array#concat.
   * @returns {String} - String of joined array.
   * @description Synonym for
   * [Array#join]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/join}.
   */
  join() {
    return this.$.join.apply(this.$, arguments);
  },

  /**
   * @member {*} Arr#last
   * @public
   * @readonly
   * @returns {*} Last array element.
   * @description The last element of the array.
   *
   * @example
   * new Arr([1, 2, 3]).last; // 3
   * new Arr([]).last;        // undefined
   */
  'get last'() {
    const array = this.$;

    return array[array.length - 1];
  },

  /**
   * @member {Number} Arr#length
   * @public
   * @readonly
   * @returns {Number} Array length.
   * @description Synonym for
   * [Array#length]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/length}.
   */
  'get length'() {
    return this.$.length;
  },

  /**
   * @member {Function} Arr#pop
   * @public
   * @returns {*} Returns deleted element.
   * @description Synonym for
   * [Array#pop]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/pop}.
   */
  pop() {
    return this.$.pop();
  },

  /**
   * @member {Function} Arr#push
   * @public
   * @param {...*} values - See
   * [Array#push]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push}.
   * @returns {Arr} Returns this.
   * @description Synonym for
   * [Array#push]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push}
   * besides returning this.
   */
  push() {
    this.$.push.apply(this.$, arguments);

    return this;
  },

  /**
   * @member {Function} Arr#reverse
   * @public
   * @returns {Arr} A wrap of the reversed array.
   * @description Reverse the array.
   *
   * @example
   * new Arr([1, 2, 3]).reverse().$; // [3, 2, 1]
   * new Arr([]).reverse().$;        // []
   */
  reverse() {
    this.$.reverse();

    return this;
  },

  /**
   * @member {Function} Arr#shift
   * @public
   * @returns {*} Returns deleted element.
   * @description Synonym for
   * [Array#shift]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/shift}.
   */
  shift() {
    return this.$.shift();
  },

  /**
   * @member {Function} Arr#shuffle
   * @public
   * @returns {Arr} A wrap of a shuffled array.
   * @description Method for shuffling.
   *
   * @example
   * new Arr([1, 2, 3, 4]).shuffle().$; // [4, 2, 3, 1]
   */
  shuffle() {
    const length = this.$.length;

    return this.forEach((value, index, array) => {
      const randomIndex = index + Math.floor((length - index) * Math.random());

      array[index] = array[randomIndex];
      array[randomIndex] = value;
    });
  },

  /**
   * @member {Function} Arr#slice
   * @param {Number} [begin = 0] - See
   * [Array#slice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice}.
   * @param {Number} [end = array.length] - See
   * [Array#slice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice}.
   * @returns {Arr} A wrap of a sliced array.
   * @description Synonym for
   * [Array#slice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice}.
   */
  slice() {
    return new this.constructor(this.$.slice.apply(this.$, arguments));
  },

  /**
   * @member {Function} Arr#sort
   * @public
   * @param {CompareFunction} [compareFunction] - See
   * [Array#sort]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}.
   * @returns {Arr} Returns this.
   * @description Synonym for
   * [Array#sort]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}.
   */
  sort(compareFunction) {
    validate([compareFunction], ['function||!'], 'Arr#sort');

    this.$.sort(compareFunction);

    return this;
  },

  /**
   * @member {Function} Arr#sortAsc
   * @public
   * @returns {Arr} Returns this.
   * @description Method for ascending sorting. Puts non-numbers first, then NaNs, then sorted values.
   *
   * @example
   * new Arr([NaN, 1, -7, '100', 5]).sortAsc().$; // ['100', NaN, -7, 1, 5]
   */
  sortAsc() {
    return this.sort(asc);
  },

  /**
   * @member {Function} Arr#sortDesc
   * @public
   * @returns {Arr} Returns this.
   * @description Method for descending sorting. Puts sorted values first, then NaNs, then non-numbers.
   *
   * @example
   * new Arr([NaN, 1, -7, '100', 5]).sortDesc().$; // [5, 1, -7, NaN, '100']
   */
  sortDesc() {
    return this.sort((y, x) => asc(x, y));
  },

  /**
   * @member {Function} Arr#splice
   * @public
   * @param {Number} [start] - See
   * [Array#splice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice}.
   * @param {Number} [deleteCount] - See
   * [Array#splice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice}.
   * @param {...*} [items] - See
   * [Array#splice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice}.
   * @returns {Arr} A wrap of return value of #splice call.
   * @description Synonym for
   * [Array#splice]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice}.
   */
  splice() {
    return new Arr(this.$.splice.apply(this.$, arguments));
  },

  /**
   * @member {Function} Arr#string
   * @public
   * @returns {String} Concatenated array.
   * @description Synonym for array.join('').
   *
   * @example
   * new Arr([1, 2, 3]).string(); // '123'
   */
  string() {
    return this.join('');
  },

  /**
   * @member {Function} Arr#unshift
   * @public
   * @param {...*} values - See
   * [Array#unshift]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift}.
   * @returns {Arr} Returns this.
   * @description Synonym for
   * [Array#unshift]
   * {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift}
   * besides returning this.
   */
  unshift() {
    this.$.unshift.apply(this.$, arguments);

    return this;
  },

  [Symbol.toStringTag]: 'Arr'
});

/**
 * @function asc
 * @private
 * @param {*} x - First value to be compared.
 * @param {*} y - Second value to be compared.
 * @returns {Number} Where to put the first element: before or after.
 */
function asc(x, y) {
  if (!isNumber(x) && !isNumber(y)) {
    return 0;
  }

  if (!isNumber(x)) {
    return -1;
  }

  if (!isNumber(y)) {
    return 1;
  }

  if (isNaN(x) && isNaN(y)) {
    return 0;
  }

  if (isNaN(x)) {
    return -1;
  }

  if (isNaN(y)) {
    return 1;
  }

  return x - y;
}

constructors[1].push({
  check: isArrayLike,
  cls: Arr
});

/**
 * @function array
 * @public
 * @param {Number} number - Length of the array.
 * @param {ArrayCallback} [callback] - If it's present it has to a function
 * that returns the element that is pushed to the new array.
 * @returns {Arr} New instance of Arr.
 * @description Method for creating new array from the length using optional callback.
 *
 * @example
 * array(3).$;               // [0, 1, 2]
 * array(3, (i) => i * 2).$; // [0, 2, 4]
 */
export function array(number, callback) {
  validate([number, callback], [['intLike', '>=0'], 'function||!']);

  const array = [];

  for (let i = 0; i < number; i++) {
    array.push(callback ? callback(i) : i);
  }

  return new Arr(array);
}

export default Arr;
<commit_msg>Arr: change @member {Function} to @method.<commit_after>/**
 * @module Arr
 * @private
 * @mixin
 * @description Exports Arr class.
 */

import Super from './Super';
import constructors from './constants/constructors';
import {
  isArrayLike, isNaN, isNumber, isString,
  toArray, validate, iterate, Symbol, defineProperties,
  inherits, checkClassInstance, possibleSuperClassReturn
} from './helpers';

/**
 * @typedef {Array|*} ArrayLike
 * @public
 * @description Array-like type.
 */

/**
 * @callback ArrayCallback
 * @public
 * @param {Number} i - Iteration index.
 */

/**
 * @callback CompareFunction
 * @public
 * @param {*} x - First value to be compared.
 * @param {*} y - Second value to be compared.
 */

/**
 * @class Arr
 * @extends Super
 * @public
 * @param {Array} [array = []] - An array to wrap.
 * @returns {Arr} Instance of Arr.
 * @description Wrap of an array.
 *
 * @example
 * new Arr([1, 2]);
 */
export function Arr(array = []) {
  checkClassInstance(this, Arr, 'Arr');

  const $this = possibleSuperClassReturn(this, Super, [toArray(array)]);

  Object.defineProperty($this, '$$', { value: array });

  return $this;

  /**
   * @member {Array} Arr#$
   * @public
   * @description Made array.
   */

  /**
   * @member {ArrayLike} Arr#$$
   * @public
   * @description Original array.
   */
}

inherits(Arr, Super);

defineProperties(Arr.prototype, {
  /**
   * @method Arr#concat
   * @public
   * @param {...(Array|*)} values - Arrays or any other values to concat the array with.
   * @returns {Arr} New instance of Arr.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
   * @description Synonym for
   * [Array#concat]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/concat}.
   */
  concat() {
    const array = toArray(this.$, true);

    iterate(arguments, (value) => {
      value = new Super(value).$;

      if (isArrayLike(value) && !isString(value)) {
        iterate(value, (value) => {
          array.push(value);
        });

        return;
      }

      array.push(value);
    });

    return new this.constructor(array);
  },

  /**
   * @method Arr#indexOf
   * @public
   * @param {*} value - Value to search.
   * @returns {Number} Index of the argument.
   * @description Almost the same as {@link Super#keyOf}. The difference is that if the value is not found
   * -1 returned instead of null and if found Number(key) returned.
   *
   * @example
   * new Arr([1, 2, 3]).indexOf(1);       // 0
   * new Arr([1, 2, 3]).indexOf('1');     // 0
   * new Arr([1, 2, 3]).indexOf(3);       // -1
   * new Arr([1, 2, NaN]).indexOf(NaN);   // 2
   */
  indexOf(value) {
    const key = this.keyOf(value);

    return key === null ? -1 : Number(key);
  },

  /**
   * @method Arr#indexOfStrict
   * @public
   * @param {*} value - Value to search.
   * @returns {Number} Index of the argument.
   * @description Almost the same as {@link Super#keyOfStrict}. The difference is that if the value is not found
   * -1 returned instead of null and if found Number(key) returned.
   *
   * @example
   * new Arr([1, 2, 3]).indexOfStrict(1);       // 0
   * new Arr([1, 2, 3]).indexOfStrict('1');     // -1
   * new Arr([1, 2, 3]).indexOfStrict(3);       // -1
   * new Arr([1, 2, NaN]).indexOfStrict(NaN);   // 2
   */
  indexOfStrict(value) {
    const key = this.keyOfStrict(value);

    return key === null ? -1 : Number(key);
  },

  /**
   * @method Arr#join
   * @public
   * @param {String} [separator = ','] - See the link.
   * @returns {String} - String of joined array.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/join
   * @description Synonym for
   * [Array#join]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/join}.
   */
  join() {
    return this.$.join.apply(this.$, arguments);
  },

  /**
   * @member {*} Arr#last
   * @public
   * @readonly
   * @returns {*} Last array element.
   * @description The last element of the array.
   *
   * @example
   * new Arr([1, 2, 3]).last; // 3
   * new Arr([]).last;        // undefined
   */
  'get last'() {
    const array = this.$;

    return array[array.length - 1];
  },

  /**
   * @member {Number} Arr#length
   * @public
   * @readonly
   * @returns {Number} Array length.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/length
   * @description Synonym for
   * [Array#length]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/length}.
   */
  'get length'() {
    return this.$.length;
  },

  /**
   * @method Arr#pop
   * @public
   * @returns {*} Returns deleted element.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
   * @description Synonym for
   * [Array#pop]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/pop}.
   */
  pop() {
    return this.$.pop();
  },

  /**
   * @method Arr#push
   * @public
   * @param {...*} [values] See the link.
   * @this {Arr}
   * @returns {Arr} Returns this.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push
   * @description Synonym for
   * [Array#push]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push}
   * besides returning this.
   */
  push() {
    this.$.push.apply(this.$, arguments);

    return this;
  },

  /**
   * @method Arr#reverse
   * @public
   * @returns {Arr} A wrap of the reversed array.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
   * @description Synonym for
   * [Array#reverse]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse}.
   */
  reverse() {
    this.$.reverse();

    return this;
  },

  /**
   * @method Arr#shift
   * @public
   * @returns {*} Returns deleted element.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
   * @description Synonym for
   * [Array#shift]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/shift}.
   */
  shift() {
    return this.$.shift();
  },

  /**
   * @method Arr#shuffle
   * @public
   * @returns {Arr} Returns this.
   * @description Method for shuffling.
   *
   * @example
   * new Arr([1, 2, 3, 4]).shuffle().$; // [4, 2, 3, 1]
   * new Arr([1, 2, 3, 4]).shuffle().$; // [1, 3, 4, 2]
   */
  shuffle() {
    const length = this.$.length;

    return this.forEach((value, index, array) => {
      const randomIndex = index + Math.floor((length - index) * Math.random());

      array[index] = array[randomIndex];
      array[randomIndex] = value;
    });
  },

  /**
   * @method Arr#slice
   * @public
   * @param {Number} [begin = 0] - See the link.
   * @param {Number} [end = array.length] - See the link.
   * @returns {Arr} A wrap of a sliced array.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
   * @description Synonym for
   * [Array#slice]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice}.
   */
  slice() {
    return new this.constructor(this.$.slice.apply(this.$, arguments));
  },

  /**
   * @method Arr#sort
   * @public
   * @param {CompareFunction} [compareFunction] - See the link.
   * @returns {Arr} Returns this.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
   * @description Synonym for
   * [Array#sort]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}.
   */
  sort(compareFunction) {
    validate([compareFunction], ['function||!'], 'Arr#sort');

    this.$.sort(compareFunction);

    return this;
  },

  /**
   * @method Arr#sortAsc
   * @public
   * @returns {Arr} Returns this.
   * @description Method for ascending sorting. Puts non-numbers first, then NaNs, then sorted values.
   *
   * @example
   * new Arr([NaN, 1, -7, '100', 5]).sortAsc().$; // ['100', NaN, -7, 1, 5]
   */
  sortAsc() {
    return this.sort(asc);
  },

  /**
   * @method Arr#sortDesc
   * @public
   * @returns {Arr} Returns this.
   * @description Method for descending sorting. Puts sorted values first, then NaNs, then non-numbers.
   *
   * @example
   * new Arr([NaN, 1, -7, '100', 5]).sortDesc().$; // [5, 1, -7, NaN, '100']
   */
  sortDesc() {
    return this.sort((y, x) => asc(x, y));
  },

  /**
   * @method Arr#splice
   * @public
   * @param {Number} [start] - See the link.
   * @param {Number} [deleteCount] - See the link.
   * @param {...*} [items] - See the link.
   * @returns {Arr} A wrap of return value of #splice call.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
   * @description Synonym for
   * [Array#splice]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice}.
   */
  splice() {
    return new Arr(this.$.splice.apply(this.$, arguments));
  },

  /**
   * @method Arr#string
   * @public
   * @returns {String} Concatenated array.
   * @description Synonym for array.join('').
   *
   * @example
   * new Arr([1, 2, 3]).string(); // '123'
   */
  string() {
    return this.join('');
  },

  /**
   * @method Arr#unshift
   * @public
   * @param {...*} [values] - See the link.
   * @returns {Arr} Returns this.
   * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
   * @description Synonym for
   * [Array#unshift]{@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift}
   * besides returning this.
   */
  unshift() {
    this.$.unshift.apply(this.$, arguments);

    return this;
  },

  [Symbol.toStringTag]: 'Arr'
});

/**
 * @function asc
 * @private
 * @param {*} x - First value to be compared.
 * @param {*} y - Second value to be compared.
 * @returns {Number} Where to put the first element: before or after.
 */
function asc(x, y) {
  if (!isNumber(x) && !isNumber(y)) {
    return 0;
  }

  if (!isNumber(x)) {
    return -1;
  }

  if (!isNumber(y)) {
    return 1;
  }

  if (isNaN(x) && isNaN(y)) {
    return 0;
  }

  if (isNaN(x)) {
    return -1;
  }

  if (isNaN(y)) {
    return 1;
  }

  return x - y;
}

constructors[1].push({
  check: isArrayLike,
  cls: Arr
});

/**
 * @function array
 * @public
 * @param {Number} number - Length of the array.
 * @param {ArrayCallback} [callback] - If it's present it has to a function
 * that returns the element that is pushed to the new array.
 * @returns {Arr} New instance of Arr.
 * @description Method for creating new array from the length using optional callback.
 *
 * @example
 * array(3).$;               // [0, 1, 2]
 * array(3, (i) => i * 2).$; // [0, 2, 4]
 */
export function array(number, callback) {
  validate([number, callback], [['intLike', '>=0'], 'function||!']);

  const array = [];

  for (let i = 0; i < number; i++) {
    array.push(callback ? callback(i) : i);
  }

  return new Arr(array);
}

export default Arr;
<|endoftext|>


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

  /**
   *  Chainable wrapper class.
   *
   *  This is the core of the entire plugin system and typically you extend
   *  functionality by adding methods to the prototype of this function.
   *
   *  However a plugin may also add static methods to the main function,
   *  see the `create` plugin for an example of adding static methods.
   *
   *  This implementation targets modern browsers (IE9+) and requires that
   *  the array methods `isArray`, `forEach` and `filter` are available,
   *  for older browsers you will need to include polyfills.
   *
   *  @param el A DOM element, array of elements or selector.
   *  @param context The context element for a selector.
   */
  function Air(el, context) {
    //window.a = 'b';
    context = context || document;
    this.dom = typeof el === 'string' ? context.querySelectorAll(el) : el;
    if(el instanceof Air) {
      this.dom = el.dom.slice(0);
    }else if(!Array.isArray(el)) {
      if(!(this.dom instanceof NodeList)) {
        this.dom = [this.dom];
      }else{
        this.dom = Array.prototype.slice.call(this.dom);
      }
    }
  }

  var proto = Air.prototype;

  /**
   *  Configuration object passed to plugin methods.
   */
  var conf = {
    main: air,
    clazz: Air,
    proto: proto,
    plugin: plugin
  };

  /**
   *  Get the number of wrapped DOM elements.
   */
  Object.defineProperty(
    proto, 'length', {get: function getLength(){return this.dom.length}});

  /**
   *  Get the DOM element at the specified index.
   */
  proto.get = function get(index) {
    if(index === undefined) return this.dom;
    return this.dom[index];
  }

  /**
   *  Static plugin method.
   *
   *  @param plugins Array of plugin functions.
   */
  function plugin(plugins) {
    if(Array.isArray(plugins)) {
      plugins.forEach(function(plugin) {
        plugin(conf);
      })
    }
  }

  /**
   *  Main function, wraps a DOM element or selector into an
   *  array of elements.
   */
  function air(el) {
    return new Air(el);
  }

  air.plugin = plugin;

  module.exports = air;
})();
<commit_msg>Pass context to constructor.<commit_after>;(function() {
  "use strict"

  /**
   *  Chainable wrapper class.
   *
   *  This is the core of the entire plugin system and typically you extend
   *  functionality by adding methods to the prototype of this function.
   *
   *  However a plugin may also add static methods to the main function,
   *  see the `create` plugin for an example of adding static methods.
   *
   *  This implementation targets modern browsers (IE9+) and requires that
   *  the array methods `isArray`, `forEach` and `filter` are available,
   *  for older browsers you will need to include polyfills.
   *
   *  @param el A DOM element, array of elements or selector.
   *  @param context The context element for a selector.
   */
  function Air(el, context) {
    //window.a = 'b';
    context = context || document;
    this.dom = typeof el === 'string' ? context.querySelectorAll(el) : el;
    if(el instanceof Air) {
      this.dom = el.dom.slice(0);
    }else if(!Array.isArray(el)) {
      if(!(this.dom instanceof NodeList)) {
        this.dom = [this.dom];
      }else{
        this.dom = Array.prototype.slice.call(this.dom);
      }
    }
  }

  var proto = Air.prototype;

  /**
   *  Configuration object passed to plugin methods.
   */
  var conf = {
    main: air,
    clazz: Air,
    proto: proto,
    plugin: plugin
  };

  /**
   *  Get the number of wrapped DOM elements.
   */
  Object.defineProperty(
    proto, 'length', {get: function getLength(){return this.dom.length}});

  /**
   *  Get the DOM element at the specified index.
   */
  proto.get = function get(index) {
    if(index === undefined) return this.dom;
    return this.dom[index];
  }

  /**
   *  Static plugin method.
   *
   *  @param plugins Array of plugin functions.
   */
  function plugin(plugins) {
    if(Array.isArray(plugins)) {
      plugins.forEach(function(plugin) {
        plugin(conf);
      })
    }
  }

  /**
   *  Main function, see the documentation for the `Air` class.
   */
  function air(el, context) {
    return new Air(el, context);
  }

  air.plugin = plugin;

  module.exports = air;
})();
<|endoftext|>


<commit_before>import apiClient from 'api-client';
import logger from 'logger';
import picker from 'picker';

export default (apikey, security, moduleOverrides = {}) => {
  if (typeof apikey !== 'string') {
    throw new Error('No apikey specified');
  }

  if (security && !(security.policy && security.signature)) {
    throw new Error('signature and policy are both required for security');
  }

  const client = moduleOverrides.apiClient || apiClient.init(apikey, security);

  logger(`Initiated with apikey ${apikey}`);

  return {
    pick(options) {
      return picker(client, options);
    },
    storeURL(url, options) {
      return client.storeURL(url, options);
    },
    transform(url, options) {
      return client.transform(url, options);
    },
    upload(file, uploadOptions, storeOptions) {
      return client.upload(file, uploadOptions, storeOptions);
    },
    retrieve(handle, options) {
      return client.retrieve(handle, options);
    },
    remove(handle) {
      return client.remove(handle);
    },
    metadata(handle, options) {
      return client.metadata(handle, options);
    },
  };
};
<commit_msg>Expose getSecurity and setSecurity methods<commit_after>import apiClient from 'api-client';
import logger from 'logger';
import picker from 'picker';

export default (apikey, security, moduleOverrides = {}) => {
  if (typeof apikey !== 'string') {
    throw new Error('No apikey specified');
  }

  if (security && !(security.policy && security.signature)) {
    throw new Error('signature and policy are both required for security');
  }

  const client = moduleOverrides.apiClient || apiClient.init(apikey, security);

  logger(`Initiated with apikey ${apikey}`);

  return {
    getSecurity() {
      return client.getSecurity();
    },
    setSecurity(sec) {
      return client.setSecurity(sec);
    },
    pick(options) {
      return picker(client, options);
    },
    storeURL(url, options) {
      return client.storeURL(url, options);
    },
    transform(url, options) {
      return client.transform(url, options);
    },
    upload(file, uploadOptions, storeOptions) {
      return client.upload(file, uploadOptions, storeOptions);
    },
    retrieve(handle, options) {
      return client.retrieve(handle, options);
    },
    remove(handle) {
      return client.remove(handle);
    },
    metadata(handle, options) {
      return client.metadata(handle, options);
    },
  };
};
<|endoftext|>