<commit_before>const fs = require("fs")
const path = require("path")
const zlib = require("zlib")

import * as ClassContext from "./class-context"
import {FileContext} from "./file-context"
import * as PHPError from "./php-error"
import PHPLint from "./php-lint"
import * as PHPType from "./php-type"
import PHPAutoloader from "./php-autoloader"
import * as ShadowTree from "./shadowtree"
import Context from "./context"
import {LintSingle} from "./lint"

/**
 * Reads JSON from a compressed PHP info data file
 *
 * @param {string} filename
 * @returns {*}
 */
function readPHPInfo(filename) {
    let compressed = fs.readFileSync(
        __dirname + "/../../data/" + filename
    )
    return JSON.parse(zlib.gunzipSync(compressed).toString("utf8"))
}

/**
 * @type {boolean} If true, autoload failure may throw. This can help with
 * debugging unexpected loads.
 */
const DEBUG_AUTOLOAD = false

/** @type {number} The maximum depth we're willing to scan files at */
const MAX_DEPTH = Infinity

/**
 * @type {string[]} From ./php-bin/php-classes | gzip > data/php-classes.json.gz
 */
const PHPClasses = Object.keys(readPHPInfo("php-classes.json.gz"))

/**
 * @type {string[]} From ./php-bin/php-interfaces | gzip > data/php-interfaces.json.gz
 */
const PHPInterfaces = readPHPInfo("php-interfaces.json.gz")

class FileResult {
    /**
     *
     * @param {string} filename
     * @param {number} depth
     */
    constructor(filename, depth) {
        this.depth = depth
        this.filename = filename
        /** @type {?Error} */
        this.error = null
        /**
         * @type {?boolean}
         */
        this.result = null
    }
}

/**
 * This defines content that's defined from the global scope, ie. everything
 * that is not anonymous.
 */
export class GlobalContext {
    /**
     * This returns autoload config from the given composer.json
     * @param {?string} filename eg. /foo/bar/composer.json
     * @param {?string} vendor_path If unset, this will be based on the filename
     * @returns {?PHPAutoloader}
     */
    static autoloadFromComposer(filename, vendor_path = null) {
        if(!filename) return null
        /** @type {string} The current module (or whole project) root */
        let current_module_path = path.dirname(filename)
        try {
            let composer_config = JSON.parse(fs.readFileSync(filename, "utf8"))
            let autoload_paths = {}
            let classmap_paths = []
            if(composer_config.autoload) {
                let psr0 = composer_config.autoload["psr-0"]
                if(psr0) {
                    Object.keys(psr0).forEach(
                        prefix => {
                            autoload_paths[prefix] = [psr0[prefix]].map(
                                path => `${current_module_path}/${path}${prefix.replace(/[_\\]/g, "/")}/`
                            )
                        }
                    )
                }
                let psr4 = composer_config.autoload["psr-4"]
                if(psr4) {
                    Object.keys(psr4).forEach(
                        prefix => {
                            autoload_paths[prefix] =
                                (psr4[prefix] instanceof Array ?
                                    psr4[prefix] :
                                    [psr4[prefix]]
                            ).map(
                                path => {
                                    if(path.match(/\/$/) || path == "") {
                                        return `${current_module_path}/${path}`
                                    } else {
                                        //console.log(`Path ${path} for ${prefix} is missing a trailing slash`)
                                        return `${current_module_path}/${path}/`
                                    }
                                }
                            )
                        }
                    )
                }
                let classmap = composer_config.autoload["classmap"]
                if(classmap) {
                    console.log(
                        "WARNING, this project uses classmap autoload, this is extremely unwise and takes non-trivial time to parse"
                    )
                    classmap_paths = classmap.map(
                        // Directory paths may be garbage here, but won't know
                        // until we try to use them.
                        path => `${current_module_path}/${path}`
                    )
                }
            }
            if(!vendor_path) {
                vendor_path = path.dirname(filename) + "/vendor"
            }
            let autoloader = new PHPAutoloader(autoload_paths, classmap_paths)
            if(composer_config.require) {
                Object.keys(composer_config.require).filter(
                    uid => uid.match(/\//)
                ).forEach(
                    uid => autoloader.add(
                        this.autoloadFromComposer(
                            `${vendor_path}/${uid}/composer.json`,
                            vendor_path
                        )
                    )
                )
            }
            return autoloader
        } catch(e) {
            if(e.errno == -2) {
                console.log(`${filename} missing, not installed?`)
                return new PHPAutoloader({})
            } else {
                throw e
            }
        }
    }

    /**
     * Builds the context
     *
     * @param {LintSingle} lint_single
     */
    constructor(lint_single) {
        /** @type {{[x: string]: ClassContext.Class}} */
        this.classes = {}
        /** @type {{[x: string]: boolean}} */
        this.filesSeen = {}
        /** @type {FileResult[]} */
        this.results = []
        /** @type {{[x: string]: ClassContext.Trait}} */
        this.traits = {}

        /** @type {?string} */
        this.workingDirectory = null
        PHPClasses.forEach(
            name => {
                if(name == "DateTime") {
                    this.addUnknownClass("\\" + name).addIdentifier(
                        "modify",
                        "public",
                        false,
                        new PHPType.Union(
                            new PHPType.Function(
                                [PHPType.Core.types.string.addTypesFrom(
                                    PHPType.Core.types.bool.withValue(false)
                                )],
                                PHPType.Core.named("\\" + name)
                            )
                        )
                    )
                } else {
                    this.addUnknownClass("\\" + name)
                }
            }
        )
        PHPInterfaces.forEach(
            name => this.addUnknownClass("\\" + name)
        )
        this.addUnknownClass("mixed")
        this.addUnknownClass("object")

        this.lintSingle = lint_single
    }

    /**
     * @type {{[x: number]: number}} The number of classes at each depth
     */
    get depthCounts() {
        let counts = {}
        this.results.forEach(fr => {
            if(!counts[fr.depth]) counts[fr.depth] = 0
            counts[fr.depth]++
        })
        return counts
    }

    /**
     * Adds a known class
     * @param {string} name Fully qualified only
     * @param {?ClassContext.Class} [superclass]
     * @param {?FileContext} [file_context]
     * @param {?{context: Context, node: ShadowTree.Class}} [warm_info]
     * @param {string[]} [interface_names]
     * @returns {ClassContext.Class}
     */
    addClass(
        name,
        superclass = null,
        file_context = null,
        warm_info = null,
        interface_names = []
    ) {
        if(
            this.classes[name] &&
            !(this.classes[name] instanceof ClassContext.UnknownClass)
        ) {
            return this.classes[name]
        } else {
            return this.classes[name] = new ClassContext.Class(
                name,
                superclass,
                file_context,
                warm_info,
                interface_names
            )
        }
    }

    /**
     * Adds a file to the result set, and calls the supplied function to check it.
     *
     * @param {string} filename
     * @param {number} depth
     * @param {function(): boolean} f
     */
    addFile(filename, depth, f) {
        if(!this.results.some(fr => fr.filename == filename)) {
            let fr = new FileResult(filename, depth)
            this.results.push(fr)
            try {
                fr.result = f()
            } catch(e) {
                fr.error = e
            }
        }
    }

    /**
     * Adds an unknown class. For when the real name isn't known and you have to
     * use a placeholder. You can get the name from the return value.
     * @param {?string} [name]
     * @returns {ClassContext.UnknownClass}
     */
    addUnknownClass(name = null) {
        if(!name) {
            name = "Unknown" + Math.random()
        }
        return this.classes[name] = new ClassContext.UnknownClass(name)
    }

    /**
     * Adds an unknown trait. For when the real name isn't known and you have to
     * use a placeholder. You can get the name from the return value.
     *
     * @param {?string} [name]
     * @returns {ClassContext.UnknownTrait}
     */
    addUnknownTrait(name = null) {
        if(!name) {
            name = "Unknown" + Math.random()
        }
        return this.traits[name] = new ClassContext.UnknownTrait(name)
    }

    /**
     * Adds a known trait
     * @param {string} name Fully qualified only
     * @param {?ClassContext.Trait} superclass
     * @param {?FileContext} file_context
     * @param {ShadowTree.Trait} trait_node
     * @returns {ClassContext.Trait}
     */
    addTrait(name, superclass, file_context, trait_node) {
        if(
            this.traits[name] &&
            !(this.traits[name] instanceof ClassContext.UnknownTrait)
        ) {
            return this.traits[name]
        } else {
            return this.traits[name] = new ClassContext.Trait(
                name,
                superclass,
                file_context,
                trait_node
            )
        }
    }

    /**
     * Adds a known interface
     * @param {string} name Fully qualified only
     * @param {?ClassContext.Class} [superclass]
     * @param {?FileContext} [file_context]
     * @returns {ClassContext.Class}
     */
    addInterface(name, superclass = null, file_context = null) {
        if(
            this.classes[name] &&
            !(this.classes[name] instanceof ClassContext.UnknownClass)
        ) {
            return this.classes[name]
        } else {
            return this.classes[name] = new ClassContext.Interface(
                name,
                superclass,
                file_context
            )
        }
    }

    /**
     * Checks a PHP file. This will typically affect what's defined in the
     * global namespace, including classes.
     * @param {string} filename
     * @param {number} [depth]
     */
    checkFile(filename, depth = 0) {
        let resolved_filename = path.resolve(filename)
        if(!this.filesSeen[resolved_filename]) {
            this.filesSeen[resolved_filename] = true
            let data = fs.readFileSync(filename, "utf8")
            let tree = PHPLint.parser.parseCode(data, filename)
            return this.lintSingle.checkTree(
                tree,
                filename,
                false,
                depth,
                null
            )
        }
    }

    /**
     * Given a path for the current file, walks up the tree looking for
     * composer.json.
     *
     * @param {string} actual_path eg. /foo/bar/lib/Baz/
     * @return {?string} eg. /foo/bar/composer.json
     */
    findComposerConfig(actual_path) {
        while(actual_path.match(/^\/+[^\/]/)) {
            if(fs.existsSync(`${actual_path}/composer.json`)) {
                return `${actual_path}/composer.json`
            }
            actual_path = path.dirname(actual_path)
        }
        return null
    }

    /**
     * Finds the class context with the given name
     * @param {string} name Fully qualified only
     * @param {FileContext} file_context
     * @param {number} [depth] The current load depth
     * @returns {?ClassContext.Class}
     */
    findClass(name, file_context, depth = 0) {
        if(name.match(/ -> /)) {
            return ClassContext.AnonymousFunction.inst
        }
        let load_depth = depth + 1
        let filename = file_context.filename
        if(this.classes.hasOwnProperty(name)) {
            let c = this.classes[name]
            if(c.fileContext) {
                let fr = this.results.find(
                    fr => fr.filename == c.fileContext.filename
                )
                if(fr && fr.depth > load_depth) {
                    fr.depth = load_depth
                }
            }
            return this.classes[name]
        } else if(load_depth > MAX_DEPTH) {
            return new ClassContext.UnknownClass(name)
        } else {
            this.addUnknownClass(name)
            // Autoload go!
            if(!this.autoloader) {
                this.autoloader = GlobalContext.autoloadFromComposer(
                    this.findComposerConfig(this.workingDirectory)
                )
            }
            if(this.autoloader) {
                let full_path = this.autoloader.findClassFile(name)
                if(full_path) {
                    this.checkFile(full_path, load_depth)
                    if(!this.classes[name]) {
                        console.log(
                            `Class ${name} not found at ${full_path}`
                        )
                        this.addUnknownClass(name)
                    }
                    return this.classes[name]
                }
            }
            if(DEBUG_AUTOLOAD) {
                console.log(this.autoloader)
                throw new PHPError.ClassLoadFailed(`Could not autoload ${name}`)
            } else {
                console.log(`Could not autoload ${name}`)
                return this.classes[name]
            }
        }
    }

    /**
     * Finds the trait context with the given name
     *
     * @param {string} name Fully qualified only
     * @param {FileContext} file_context
     * @param {number} [depth] The current load depth
     * @returns {?ClassContext.Trait}
     */
    findTrait(name, file_context, depth = 0) {
        let load_depth = depth + 1
        let filename = file_context.filename
        if(this.traits.hasOwnProperty(name)) {
            let t = this.traits[name]
            if(t.fileContext) {
                let fr = this.results.find(
                    fr => fr.filename == t.fileContext.filename
                )
                if(fr && fr.depth > load_depth) {
                    fr.depth = load_depth
                }
            }
            return this.traits[name]
        } else {
            this.addUnknownTrait(name)
            // Autoload go!
            if(!this.autoloader) {
                this.autoloader = GlobalContext.autoloadFromComposer(
                    this.findComposerConfig(this.workingDirectory)
                )
            }
            if(this.autoloader) {
                let full_path = this.autoloader.findClassFile(name)
                if(full_path) {
                    this.checkFile(full_path, load_depth)
                    if(!this.traits[name]) {
                        console.log(
                            `Trait ${name} not found at ${full_path}`
                        )
                        this.addUnknownTrait(name)
                    }
                    return this.traits[name]
                }
            }
            if(DEBUG_AUTOLOAD) {
                console.log(this.autoloader)
                throw new PHPError.ClassLoadFailed(`Could not autoload ${name}`)
            } else {
                console.log(`Could not autoload ${name}`)
                return this.traits[name]
            }
        }
    }
}<commit_msg>Say which file uses classmap<commit_after>const fs = require("fs")
const path = require("path")
const zlib = require("zlib")

import * as ClassContext from "./class-context"
import {FileContext} from "./file-context"
import * as PHPError from "./php-error"
import PHPLint from "./php-lint"
import * as PHPType from "./php-type"
import PHPAutoloader from "./php-autoloader"
import * as ShadowTree from "./shadowtree"
import Context from "./context"
import {LintSingle} from "./lint"

/**
 * Reads JSON from a compressed PHP info data file
 *
 * @param {string} filename
 * @returns {*}
 */
function readPHPInfo(filename) {
    let compressed = fs.readFileSync(
        __dirname + "/../../data/" + filename
    )
    return JSON.parse(zlib.gunzipSync(compressed).toString("utf8"))
}

/**
 * @type {boolean} If true, autoload failure may throw. This can help with
 * debugging unexpected loads.
 */
const DEBUG_AUTOLOAD = false

/** @type {number} The maximum depth we're willing to scan files at */
const MAX_DEPTH = Infinity

/**
 * @type {string[]} From ./php-bin/php-classes | gzip > data/php-classes.json.gz
 */
const PHPClasses = Object.keys(readPHPInfo("php-classes.json.gz"))

/**
 * @type {string[]} From ./php-bin/php-interfaces | gzip > data/php-interfaces.json.gz
 */
const PHPInterfaces = readPHPInfo("php-interfaces.json.gz")

class FileResult {
    /**
     *
     * @param {string} filename
     * @param {number} depth
     */
    constructor(filename, depth) {
        this.depth = depth
        this.filename = filename
        /** @type {?Error} */
        this.error = null
        /**
         * @type {?boolean}
         */
        this.result = null
    }
}

/**
 * This defines content that's defined from the global scope, ie. everything
 * that is not anonymous.
 */
export class GlobalContext {
    /**
     * This returns autoload config from the given composer.json
     * @param {?string} filename eg. /foo/bar/composer.json
     * @param {?string} vendor_path If unset, this will be based on the filename
     * @returns {?PHPAutoloader}
     */
    static autoloadFromComposer(filename, vendor_path = null) {
        if(!filename) return null
        /** @type {string} The current module (or whole project) root */
        let current_module_path = path.dirname(filename)
        try {
            let composer_config = JSON.parse(fs.readFileSync(filename, "utf8"))
            let autoload_paths = {}
            let classmap_paths = []
            if(composer_config.autoload) {
                let psr0 = composer_config.autoload["psr-0"]
                if(psr0) {
                    Object.keys(psr0).forEach(
                        prefix => {
                            autoload_paths[prefix] = [psr0[prefix]].map(
                                path => `${current_module_path}/${path}${prefix.replace(/[_\\]/g, "/")}/`
                            )
                        }
                    )
                }
                let psr4 = composer_config.autoload["psr-4"]
                if(psr4) {
                    Object.keys(psr4).forEach(
                        prefix => {
                            autoload_paths[prefix] =
                                (psr4[prefix] instanceof Array ?
                                    psr4[prefix] :
                                    [psr4[prefix]]
                            ).map(
                                path => {
                                    if(path.match(/\/$/) || path == "") {
                                        return `${current_module_path}/${path}`
                                    } else {
                                        //console.log(`Path ${path} for ${prefix} is missing a trailing slash`)
                                        return `${current_module_path}/${path}/`
                                    }
                                }
                            )
                        }
                    )
                }
                let classmap = composer_config.autoload["classmap"]
                if(classmap) {
                    console.log(
                        `WARNING, this project uses classmap autoload at ${filename}, this is extremely unwise and takes non-trivial time to parse`
                    )
                    classmap_paths = classmap.map(
                        // Directory paths may be garbage here, but won't know
                        // until we try to use them.
                        path => `${current_module_path}/${path}`
                    )
                }
            }
            if(!vendor_path) {
                vendor_path = path.dirname(filename) + "/vendor"
            }
            let autoloader = new PHPAutoloader(autoload_paths, classmap_paths)
            if(composer_config.require) {
                Object.keys(composer_config.require).filter(
                    uid => uid.match(/\//)
                ).forEach(
                    uid => autoloader.add(
                        this.autoloadFromComposer(
                            `${vendor_path}/${uid}/composer.json`,
                            vendor_path
                        )
                    )
                )
            }
            return autoloader
        } catch(e) {
            if(e.errno == -2) {
                console.log(`${filename} missing, not installed?`)
                return new PHPAutoloader({})
            } else {
                throw e
            }
        }
    }

    /**
     * Builds the context
     *
     * @param {LintSingle} lint_single
     */
    constructor(lint_single) {
        /** @type {{[x: string]: ClassContext.Class}} */
        this.classes = {}
        /** @type {{[x: string]: boolean}} */
        this.filesSeen = {}
        /** @type {FileResult[]} */
        this.results = []
        /** @type {{[x: string]: ClassContext.Trait}} */
        this.traits = {}

        /** @type {?string} */
        this.workingDirectory = null
        PHPClasses.forEach(
            name => {
                if(name == "DateTime") {
                    this.addUnknownClass("\\" + name).addIdentifier(
                        "modify",
                        "public",
                        false,
                        new PHPType.Union(
                            new PHPType.Function(
                                [PHPType.Core.types.string.addTypesFrom(
                                    PHPType.Core.types.bool.withValue(false)
                                )],
                                PHPType.Core.named("\\" + name)
                            )
                        )
                    )
                } else {
                    this.addUnknownClass("\\" + name)
                }
            }
        )
        PHPInterfaces.forEach(
            name => this.addUnknownClass("\\" + name)
        )
        this.addUnknownClass("mixed")
        this.addUnknownClass("object")

        this.lintSingle = lint_single
    }

    /**
     * @type {{[x: number]: number}} The number of classes at each depth
     */
    get depthCounts() {
        let counts = {}
        this.results.forEach(fr => {
            if(!counts[fr.depth]) counts[fr.depth] = 0
            counts[fr.depth]++
        })
        return counts
    }

    /**
     * Adds a known class
     * @param {string} name Fully qualified only
     * @param {?ClassContext.Class} [superclass]
     * @param {?FileContext} [file_context]
     * @param {?{context: Context, node: ShadowTree.Class}} [warm_info]
     * @param {string[]} [interface_names]
     * @returns {ClassContext.Class}
     */
    addClass(
        name,
        superclass = null,
        file_context = null,
        warm_info = null,
        interface_names = []
    ) {
        if(
            this.classes[name] &&
            !(this.classes[name] instanceof ClassContext.UnknownClass)
        ) {
            return this.classes[name]
        } else {
            return this.classes[name] = new ClassContext.Class(
                name,
                superclass,
                file_context,
                warm_info,
                interface_names
            )
        }
    }

    /**
     * Adds a file to the result set, and calls the supplied function to check it.
     *
     * @param {string} filename
     * @param {number} depth
     * @param {function(): boolean} f
     */
    addFile(filename, depth, f) {
        if(!this.results.some(fr => fr.filename == filename)) {
            let fr = new FileResult(filename, depth)
            this.results.push(fr)
            try {
                fr.result = f()
            } catch(e) {
                fr.error = e
            }
        }
    }

    /**
     * Adds an unknown class. For when the real name isn't known and you have to
     * use a placeholder. You can get the name from the return value.
     * @param {?string} [name]
     * @returns {ClassContext.UnknownClass}
     */
    addUnknownClass(name = null) {
        if(!name) {
            name = "Unknown" + Math.random()
        }
        return this.classes[name] = new ClassContext.UnknownClass(name)
    }

    /**
     * Adds an unknown trait. For when the real name isn't known and you have to
     * use a placeholder. You can get the name from the return value.
     *
     * @param {?string} [name]
     * @returns {ClassContext.UnknownTrait}
     */
    addUnknownTrait(name = null) {
        if(!name) {
            name = "Unknown" + Math.random()
        }
        return this.traits[name] = new ClassContext.UnknownTrait(name)
    }

    /**
     * Adds a known trait
     * @param {string} name Fully qualified only
     * @param {?ClassContext.Trait} superclass
     * @param {?FileContext} file_context
     * @param {ShadowTree.Trait} trait_node
     * @returns {ClassContext.Trait}
     */
    addTrait(name, superclass, file_context, trait_node) {
        if(
            this.traits[name] &&
            !(this.traits[name] instanceof ClassContext.UnknownTrait)
        ) {
            return this.traits[name]
        } else {
            return this.traits[name] = new ClassContext.Trait(
                name,
                superclass,
                file_context,
                trait_node
            )
        }
    }

    /**
     * Adds a known interface
     * @param {string} name Fully qualified only
     * @param {?ClassContext.Class} [superclass]
     * @param {?FileContext} [file_context]
     * @returns {ClassContext.Class}
     */
    addInterface(name, superclass = null, file_context = null) {
        if(
            this.classes[name] &&
            !(this.classes[name] instanceof ClassContext.UnknownClass)
        ) {
            return this.classes[name]
        } else {
            return this.classes[name] = new ClassContext.Interface(
                name,
                superclass,
                file_context
            )
        }
    }

    /**
     * Checks a PHP file. This will typically affect what's defined in the
     * global namespace, including classes.
     * @param {string} filename
     * @param {number} [depth]
     */
    checkFile(filename, depth = 0) {
        let resolved_filename = path.resolve(filename)
        if(!this.filesSeen[resolved_filename]) {
            this.filesSeen[resolved_filename] = true
            let data = fs.readFileSync(filename, "utf8")
            let tree = PHPLint.parser.parseCode(data, filename)
            return this.lintSingle.checkTree(
                tree,
                filename,
                false,
                depth,
                null
            )
        }
    }

    /**
     * Given a path for the current file, walks up the tree looking for
     * composer.json.
     *
     * @param {string} actual_path eg. /foo/bar/lib/Baz/
     * @return {?string} eg. /foo/bar/composer.json
     */
    findComposerConfig(actual_path) {
        while(actual_path.match(/^\/+[^\/]/)) {
            if(fs.existsSync(`${actual_path}/composer.json`)) {
                return `${actual_path}/composer.json`
            }
            actual_path = path.dirname(actual_path)
        }
        return null
    }

    /**
     * Finds the class context with the given name
     * @param {string} name Fully qualified only
     * @param {FileContext} file_context
     * @param {number} [depth] The current load depth
     * @returns {?ClassContext.Class}
     */
    findClass(name, file_context, depth = 0) {
        if(name.match(/ -> /)) {
            return ClassContext.AnonymousFunction.inst
        }
        let load_depth = depth + 1
        let filename = file_context.filename
        if(this.classes.hasOwnProperty(name)) {
            let c = this.classes[name]
            if(c.fileContext) {
                let fr = this.results.find(
                    fr => fr.filename == c.fileContext.filename
                )
                if(fr && fr.depth > load_depth) {
                    fr.depth = load_depth
                }
            }
            return this.classes[name]
        } else if(load_depth > MAX_DEPTH) {
            return new ClassContext.UnknownClass(name)
        } else {
            this.addUnknownClass(name)
            // Autoload go!
            if(!this.autoloader) {
                this.autoloader = GlobalContext.autoloadFromComposer(
                    this.findComposerConfig(this.workingDirectory)
                )
            }
            if(this.autoloader) {
                let full_path = this.autoloader.findClassFile(name)
                if(full_path) {
                    this.checkFile(full_path, load_depth)
                    if(!this.classes[name]) {
                        console.log(
                            `Class ${name} not found at ${full_path}`
                        )
                        this.addUnknownClass(name)
                    }
                    return this.classes[name]
                }
            }
            if(DEBUG_AUTOLOAD) {
                console.log(this.autoloader)
                throw new PHPError.ClassLoadFailed(`Could not autoload ${name}`)
            } else {
                console.log(`Could not autoload ${name}`)
                return this.classes[name]
            }
        }
    }

    /**
     * Finds the trait context with the given name
     *
     * @param {string} name Fully qualified only
     * @param {FileContext} file_context
     * @param {number} [depth] The current load depth
     * @returns {?ClassContext.Trait}
     */
    findTrait(name, file_context, depth = 0) {
        let load_depth = depth + 1
        let filename = file_context.filename
        if(this.traits.hasOwnProperty(name)) {
            let t = this.traits[name]
            if(t.fileContext) {
                let fr = this.results.find(
                    fr => fr.filename == t.fileContext.filename
                )
                if(fr && fr.depth > load_depth) {
                    fr.depth = load_depth
                }
            }
            return this.traits[name]
        } else {
            this.addUnknownTrait(name)
            // Autoload go!
            if(!this.autoloader) {
                this.autoloader = GlobalContext.autoloadFromComposer(
                    this.findComposerConfig(this.workingDirectory)
                )
            }
            if(this.autoloader) {
                let full_path = this.autoloader.findClassFile(name)
                if(full_path) {
                    this.checkFile(full_path, load_depth)
                    if(!this.traits[name]) {
                        console.log(
                            `Trait ${name} not found at ${full_path}`
                        )
                        this.addUnknownTrait(name)
                    }
                    return this.traits[name]
                }
            }
            if(DEBUG_AUTOLOAD) {
                console.log(this.autoloader)
                throw new PHPError.ClassLoadFailed(`Could not autoload ${name}`)
            } else {
                console.log(`Could not autoload ${name}`)
                return this.traits[name]
            }
        }
    }
}<|endoftext|>


<commit_before>import actionTypes from '../../action-types';
import updateField from '../../utils/update-field';
import mapValues from '../../utils/map-values';
import initialFieldState from '../../constants/initial-field-state';
import i from 'icepick';

const resetFieldState = (field) => {
  if (field.$form) return mapValues(field, resetFieldState);

  return i.merge(initialFieldState, {
    value: field.initialValue,
    model: field.model,
    intents: [{ type: 'validate' }],
  });
};

export default function resetActionReducer(state, action, localPath) {
  if (action.type !== actionTypes.RESET
    && action.type !== actionTypes.SET_INITIAL) {
    return state;
  }

  return updateField(state, localPath, resetFieldState, resetFieldState);
}
<commit_msg>add fix for undefined property $form of null which happens in obscure circumstances that I'm still trying to write a concrete test for<commit_after>import actionTypes from '../../action-types';
import updateField from '../../utils/update-field';
import mapValues from '../../utils/map-values';
import initialFieldState from '../../constants/initial-field-state';
import isPlainObject from 'lodash/isPlainObject';
import i from 'icepick';

const resetFieldState = (field, key) => {
  if (!isPlainObject(field) || key === '$form') return field;

  if (field.$form) return mapValues(field, resetFieldState);

  return i.merge(initialFieldState, {
    value: field.initialValue,
    model: field.model,
    intents: [{ type: 'validate' }],
  });
};

export default function resetActionReducer(state, action, localPath) {
  if (action.type !== actionTypes.RESET
    && action.type !== actionTypes.SET_INITIAL) {
    return state;
  }

  return updateField(state, localPath, resetFieldState, resetFieldState);
}
<|endoftext|>


<commit_before>const keyboard = {
  "a":      65,
  "z":      90,
  "black":  190,
  "delete": 8,
  "left":   37,
  "up":     38,
  "right":  39,
  "down":   40
};

const dir = {
  across: "across",
  down: "down"
};
const crosswordSize = 15;
createGrid(crosswordSize);

var isSymmetrical = true;
var activeRow = 0;
var activeCol = 0;
var lastDirection = DIR.ACROSS;
const grid = document.getElementById("grid");
const squares = grid.querySelectorAll('td');

for (const square of squares) {
  square.addEventListener('click', mouseInput);
}

// Handle all keyboard input
window.addEventListener('keydown', function (e) {
  var activeCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
  const symRow = crosswordSize - 1 - activeRow;
  const symCol = crosswordSize - 1 - activeCol;
  const symmetricalCell = grid.querySelector('[data-row="' + symRow + '"]').querySelector('[data-col="' + symCol + '"]');

  // If the input is different from what's already in the square...
  if (activeCell.lastChild.innerHTML != String.fromCharCode(e.which)) {
    if (e.which >= keyboard.a && e.which <= keyboard.z) {
        activeCell.lastChild.innerHTML = String.fromCharCode(e.which);
        if (activeCell.className.search("black") > -1) {
          activeCell.className = activeCell.className.replace("black", "").trim();
          if (isSymmetrical == true) {
            symmetricalCell.lastChild.innerHTML = null;
            symmetricalCell.className = symmetricalCell.className.replace("black", "").trim();
          }
        }
    } else if (e.which == keyboard.black) {
        activeCell.lastChild.innerHTML = ".";
        activeCell.className = (activeCell.className + " black").trim();
        if (isSymmetrical == true) {
          symmetricalCell.lastChild.innerHTML = ".";
          symmetricalCell.className = (symmetricalCell.className + " black").trim();
        }
    } else if (e.which == keyboard.delete) {
        activeCell.lastChild.innerHTML = null;
        if (activeCell.className.search("black") > -1) {
          activeCell.className = activeCell.className.replace("black", "").trim();
          if (isSymmetrical == true) {
            symmetricalCell.lastChild.innerHTML = null;
            symmetricalCell.className = symmetricalCell.className.replace("black", "").trim();
          }
        }
    } else if (e.which >= keyboard.left && e.which <= keyboard.down) {
        const previousCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
        previousCell.className = previousCell.className.replace("active", "");
        switch (e.which) {
          case keyboard.left:
            activeCol = (activeCol == 0) ? activeCol : activeCol - 1;
            lastDirection = dir.across;
            break;
          case keyboard.up:
            activeRow = (activeRow == 0) ? activeRow : activeRow - 1;
            lastDirection = dir.down;
            break;
          case keyboard.right:
            activeCol = (activeCol == crosswordSize - 1) ? activeCol : Number(activeCol) + 1;
            lastDirection = dir.across;
            break;
          case keyboard.down:
            activeRow = (activeRow == crosswordSize - 1) ? activeRow : Number(activeRow) + 1;
            lastDirection = dir.down;
            break;
        }
        console.log("[" + activeRow + "," + activeCol + "]");
        console.log(lastDirection);
        activeCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
        activeCell.className = (activeCell.className + " active").trim();
    }
    console.log(activeCell.lastChild.innerHTML);
    updateLabels(crosswordSize);
  }
});

function mouseInput() {
  const previousCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
  // console.log("Previous cursor: [" + previousCell.parentNode.dataset.row + "," + previousCell.dataset.col + "]");
  previousCell.className = previousCell.className.replace("active", "");
  const activeCell = event.currentTarget;
  activeRow = activeCell.parentNode.dataset.row;
  activeCol = activeCell.dataset.col;
  console.log("[" + activeRow + "," + activeCol + "]");
  activeCell.className = (activeCell.className + " active").trim();
}

function createGrid(size) {
  const rows = size;
  const cols = size;
  var table = document.createElement("TABLE");
  table.setAttribute("id", "grid");
  document.body.appendChild(table);

	for (i = 0; i < rows; i++) {
    	var row = document.createElement("TR");
    	row.setAttribute("data-row", i);
    	document.getElementById("grid").appendChild(row);

		for (j = 0; j < cols; j++) {
		    var col = document.createElement("TD");
        col.setAttribute("data-col", j);

        var label = document.createElement("DIV");
        label.setAttribute("class", "label");
        var labelContent = document.createTextNode("");

        var fill = document.createElement("DIV");
        fill.setAttribute("class", "fill");
        var fillContent = document.createTextNode(randomLetter());

    		// var t = document.createTextNode("[" + i + "," + j + "]");
        label.appendChild(labelContent);
        fill.appendChild(fillContent);
        col.appendChild(label);
        col.appendChild(fill);
    		document.querySelector('[data-row="' + i + '"]').appendChild(col);
      }
  }
  updateLabels(size);
}

function updateLabels(size) {
  var count = 1;
  var increment = false;
  const rows = size;
  const cols = size;
  const grid = document.getElementById("grid");

  for (i = 0; i < rows; i++) {
    for (j = 0; j < cols; j++) {
      increment = false;
      // if the cell isn't 'black'
      var currentCell = grid.querySelector('[data-row="' + i + '"]').querySelector('[data-col="' + j + '"]');
      if (currentCell.className.search("black") == -1) {
        // if the row is 0, increment the clue number
        if (i == 0) {
          increment = true;
        // else if the square above me is black, increment
        } else {
          upCell = grid.querySelector('[data-row="' + (i - 1) + '"]').querySelector('[data-col="' + j + '"]');
          if (upCell.className.search("black") > -1) {
            increment = true;
          }
        }
        // if the column is 0, increment
        if (j == 0) {
          increment = true;
        // else if the square to my left is black, increment
        } else {
          leftCell = grid.querySelector('[data-row="' + i + '"]').querySelector('[data-col="' + (j - 1) + '"]');
          if (leftCell.className.search("black") > -1) {
            increment = true;
          }
        }
      }
      if (increment == true) {
        currentCell.firstChild.innerHTML = count;
        count++;
        increment = false;
      } else {
        currentCell.firstChild.innerHTML = "";
      }
    }
  }
}

function randomNumber(min, max) {
  return Math.floor(Math.random() * max) + min;
}

function randomLetter() {
  var alphabet = "AAAAAAAAABBCCDDDDEEEEEEEEEEEEFFGGGHHIIIIIIIIIJKLLLLMMNNNNNNOOOOOOOOPPQRRRRRRSSSSSSTTTTTTUUUUVVWWXYYZ";
  var random = randomNumber(0, 100);
  return alphabet.substring(random, random + 1);
}

// window.alert("This is how you create an alert.")
// document.write("This is how you write to the HTML document.")
<commit_msg>Added leading/trailing cursor movement.<commit_after>const keyboard = {
  "a":      65,
  "z":      90,
  "black":  190,
  "delete": 8,
  "left":   37,
  "up":     38,
  "right":  39,
  "down":   40
};
const dir = {
  across: "across",
  down: "down"
};
const crosswordSize = 15;
createGrid(crosswordSize);

var isSymmetrical = true;
var activeRow = 0;
var activeCol = 0;
var lastDirection = dir.across;
const grid = document.getElementById("grid");
const squares = grid.querySelectorAll('td');

for (const square of squares) {
  square.addEventListener('click', mouseHandler);
}
window.addEventListener('keydown', keyboardHandler);

//____________________
// F U N C T I O N S

function mouseHandler() {
  const previousCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
  // console.log("Previous cursor: [" + previousCell.parentNode.dataset.row + "," + previousCell.dataset.col + "]");
  previousCell.className = previousCell.className.replace("active", "");
  const activeCell = event.currentTarget;
  activeRow = activeCell.parentNode.dataset.row;
  activeCol = activeCell.dataset.col;
  console.log("[" + activeRow + "," + activeCol + "]");
  activeCell.className = (activeCell.className + " active").trim();
}

function keyboardHandler(e) {
  var activeCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
  const symRow = crosswordSize - 1 - activeRow;
  const symCol = crosswordSize - 1 - activeCol;
  const symmetricalCell = grid.querySelector('[data-row="' + symRow + '"]').querySelector('[data-col="' + symCol + '"]');

  // If the input is different from what's already in the square...
  // if (activeCell.lastChild.innerHTML != String.fromCharCode(e.which)) {
    if (e.which >= keyboard.a && e.which <= keyboard.z) {
        activeCell.lastChild.innerHTML = String.fromCharCode(e.which);
        if (activeCell.className.search("black") > -1) {
          activeCell.className = activeCell.className.replace("black", "").trim();
          if (isSymmetrical == true) {
            symmetricalCell.lastChild.innerHTML = null;
            symmetricalCell.className = symmetricalCell.className.replace("black", "").trim();
          }
        }
        e = new Event('keydown');
        if (lastDirection == dir.across) {
          e.which = keyboard.right;
        } else {
          e.which = keyboard.down;
        }
        keyboardHandler(e);
    } else if (e.which == keyboard.black) {
        activeCell.lastChild.innerHTML = ".";
        activeCell.className = (activeCell.className + " black").trim();
        if (isSymmetrical == true) {
          symmetricalCell.lastChild.innerHTML = ".";
          symmetricalCell.className = (symmetricalCell.className + " black").trim();
        }
    } else if (e.which == keyboard.delete) {
        activeCell.lastChild.innerHTML = null;
        if (activeCell.className.search("black") > -1) {
          activeCell.className = activeCell.className.replace("black", "").trim();
          if (isSymmetrical == true) {
            symmetricalCell.lastChild.innerHTML = null;
            symmetricalCell.className = symmetricalCell.className.replace("black", "").trim();
          }
        }
        e = new Event('keydown');
        if (lastDirection == dir.across) {
          e.which = keyboard.left;
        } else {
          e.which = keyboard.up;
        }
        keyboardHandler(e);
    } else if (e.which >= keyboard.left && e.which <= keyboard.down) {
        const previousCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
        previousCell.className = previousCell.className.replace("active", "");
        switch (e.which) {
          case keyboard.left:
            activeCol = (activeCol == 0) ? activeCol : activeCol - 1;
            lastDirection = dir.across;
            break;
          case keyboard.up:
            activeRow = (activeRow == 0) ? activeRow : activeRow - 1;
            lastDirection = dir.down;
            break;
          case keyboard.right:
            activeCol = (activeCol == crosswordSize - 1) ? activeCol : Number(activeCol) + 1;
            lastDirection = dir.across;
            break;
          case keyboard.down:
            activeRow = (activeRow == crosswordSize - 1) ? activeRow : Number(activeRow) + 1;
            lastDirection = dir.down;
            break;
        }
        console.log("[" + activeRow + "," + activeCol + "]");
        // console.log(lastDirection);
        activeCell = grid.querySelector('[data-row="' + activeRow + '"]').querySelector('[data-col="' + activeCol + '"]');
        activeCell.className = (activeCell.className + " active").trim();
    }
    console.log(activeCell.lastChild.innerHTML);
    updateLabels(crosswordSize);
  // }
}

function createGrid(size) {
  const rows = size;
  const cols = size;
  var table = document.createElement("TABLE");
  table.setAttribute("id", "grid");
  document.body.appendChild(table);

	for (var i = 0; i < rows; i++) {
    	var row = document.createElement("TR");
    	row.setAttribute("data-row", i);
    	document.getElementById("grid").appendChild(row);

		for (var j = 0; j < cols; j++) {
		    var col = document.createElement("TD");
        col.setAttribute("data-col", j);

        var label = document.createElement("DIV");
        label.setAttribute("class", "label");
        var labelContent = document.createTextNode("");

        var fill = document.createElement("DIV");
        fill.setAttribute("class", "fill");
        var fillContent = document.createTextNode(randomLetter());

    		// var t = document.createTextNode("[" + i + "," + j + "]");
        label.appendChild(labelContent);
        fill.appendChild(fillContent);
        col.appendChild(label);
        col.appendChild(fill);
    		document.querySelector('[data-row="' + i + '"]').appendChild(col);
      }
  }
  updateLabels(size);
}

function updateLabels(size) {
  var count = 1;
  var increment = false;
  const rows = size;
  const cols = size;
  const grid = document.getElementById("grid");

  for (var i = 0; i < rows; i++) {
    for (var j = 0; j < cols; j++) {
      increment = false;
      // if the cell isn't 'black'
      var currentCell = grid.querySelector('[data-row="' + i + '"]').querySelector('[data-col="' + j + '"]');
      if (currentCell.className.search("black") == -1) {
        // if the row is 0, increment the clue number
        if (i == 0) {
          increment = true;
        // else if the square above me is black, increment
        } else {
          upCell = grid.querySelector('[data-row="' + (i - 1) + '"]').querySelector('[data-col="' + j + '"]');
          if (upCell.className.search("black") > -1) {
            increment = true;
          }
        }
        // if the column is 0, increment
        if (j == 0) {
          increment = true;
        // else if the square to my left is black, increment
        } else {
          leftCell = grid.querySelector('[data-row="' + i + '"]').querySelector('[data-col="' + (j - 1) + '"]');
          if (leftCell.className.search("black") > -1) {
            increment = true;
          }
        }
      }
      if (increment == true) {
        currentCell.firstChild.innerHTML = count;
        count++;
        increment = false;
      } else {
        currentCell.firstChild.innerHTML = "";
      }
    }
  }
}

function pressVirtualKey(key) {
  var e = new Event('keydown');
  e.keyCode = key;
  document.dispatchEvent(e);
  console.log("dispatched key event..." + e.keyCode);
}

function randomNumber(min, max) {
  return Math.floor(Math.random() * max) + min;
}

function randomLetter() {
  var alphabet = "AAAAAAAAABBCCDDDDEEEEEEEEEEEEFFGGGHHIIIIIIIIIJKLLLLMMNNNNNNOOOOOOOOPPQRRRRRRSSSSSSTTTTTTUUUUVVWWXYYZ";
  var random = randomNumber(0, 100);
  return alphabet.substring(random, random + 1);
}

// window.alert("This is how you create an alert.")
// document.write("This is how you write to the HTML document.")
<|endoftext|>


<commit_before>;(async () => {
  const podName = process.env.POD_NAME
  const podNamespace = process.env.POD_NAMESPACE
  const podNameExists = !!podName
  const podNamespaceExists = !!podNamespace
  const isK8s = podNameExists && podNamespaceExists
  process.env.isKubernetes = isK8s
  if (!isK8s) {
    return
  }

  const { readFile } = require('fs')
  const { promisify } = require('util')
  const readFileAsync = promisify(readFile)
  const CGROUP_FILE = '/proc/self/cgroup'

  let counter = 0
  const { KubeConfig } = require('kubernetes-client')
  const Client = require('kubernetes-client').Client
  const Request = require('kubernetes-client/backends/request')
  const kubeconfig = new KubeConfig()

  if (process.env.KUBERNETES_PORT_443_TCP !== undefined) {
    kubeconfig.loadFromCluster()
  } else {
    kubeconfig.loadFromDefault()
  }

  const client = new Client({
    backend: new Request({ kubeconfig }),
    version: '1.13'
  })

  try {
    await client.loadSpec()
  } catch (error) {
    console.error('Error in k8s client', error)
  }

  const parseImageRegex = /^(\S+?\.\S+?\/|\S+?:\d+\/){0,1}(\S+?):(\S+?){0,1}(@\S+?){0,1}$/i
  function parseImage (image) {
    const imageObj = { name: image }
    const parsedImage = parseImageRegex.exec(image)
    if (parsedImage) {
      if (parsedImage.length > 3) {
        if (parsedImage[1]) {
          imageObj.registry = parsedImage[1]
        }
        imageObj.name = parsedImage[2]
        imageObj.tag = parsedImage[3]
        if (parsedImage[4] !== undefined) {
          imageObj.digest = parsedImage[4].substring(1, parsedImage[4].length)
        }
      }
    }
    return imageObj
  }

  // 1:name=systemd:/kubepods/besteffort/podff89dddf-c58b-4b31-a791-9ca5ec31b803/b50973d3d4fe9a1dbc2a13b9731749f220961cd2cc1040c7285516bb132d0f27
  const getId = data => {
    if (!data) {
      return null
    }

    let id = null

    // Break each line up
    const lines = data.split('\n')

    // look for the id
    lines.some(line => {
      if (!line.includes('name=systemd')) {
        return false
      }

      const path = line.split('/')
      id = path.pop()
      if (id) {
        return true
      }

      return false
    })

    return id
  }

  const getContainerId = async () => {
    try {
      const cgroups = await readFileAsync(CGROUP_FILE, 'utf8')
      console.log(
        'cgroups in kubernetes.js: ',
        cgroups
      )
      return getId(cgroups)
    } catch (e) {
      console.log(
        'cgroupContainerId > Error in kubernetes.js: ',
        e
      )
      return null
    }
  }

  const getPod = async ({ podNamespace, podName }) => {
    return await client.api.v1
      .namespaces(podNamespace)
      .pods(podName)
      .get()
  }

  try {
    // Ping Cgroups to get ContainerId
    const containerIdFromCgroup = await getContainerId()
    console.log(
      'containerIdFromCgroup in kubernetes.js: ',
      containerIdFromCgroup
    )

    // Ping K8s API to get Pod data
    const res = await getPod({ podNamespace, podName })

    // Destructure response body object
    const {
      spec: { nodeName },
      status: { containerStatuses }
    } = res.body

    console.log('containerStatuses in kubernetes.js: ', containerStatuses)
    // Filter out the container that the spm-agent is running in
    const filteredContainer = containerStatuses
      .map(cs => {
        const obj = { container: {}, image: {} }
        obj.container.name = cs.name
        obj.image = parseImage(cs.image)
        obj.image.digest = cs.imageID
          .replace('docker-pullable://', '')
          .replace(obj.image.name, '')
          .replace('@', '')
        obj.container.id = cs.containerID.replace('docker://', '')
        return obj
      })
      .filter(c => c.container.id === containerIdFromCgroup)
      .pop()

    console.log('filteredContainer in kubernetes.js: ', filteredContainer)

    const kubernetesTags = {
      os: {
        host: nodeName
      },
      ...filteredContainer
    }

    console.log('kubernetesTags in kubernetes.js: ', kubernetesTags)
    process.env.kubernetesTags = JSON.stringify(kubernetesTags)
  } catch (error) {
    console.log('error in kubernetes.js: ', error)
  }
})()
<commit_msg>k8s cleanup<commit_after>;(async () => {
  const podName = process.env.POD_NAME
  const podNamespace = process.env.POD_NAMESPACE
  const podNameExists = !!podName
  const podNamespaceExists = !!podNamespace
  const isK8s = podNameExists && podNamespaceExists
  process.env.isKubernetes = isK8s
  if (!isK8s) {
    return
  }

  const { readFile } = require('fs')
  const { promisify } = require('util')
  const readFileAsync = promisify(readFile)
  const CGROUP_FILE = '/proc/self/cgroup'

  let counter = 0
  const { KubeConfig } = require('kubernetes-client')
  const Client = require('kubernetes-client').Client
  const Request = require('kubernetes-client/backends/request')
  const kubeconfig = new KubeConfig()

  if (process.env.KUBERNETES_PORT_443_TCP !== undefined) {
    kubeconfig.loadFromCluster()
  } else {
    kubeconfig.loadFromDefault()
  }

  const client = new Client({
    backend: new Request({ kubeconfig }),
    version: '1.13'
  })

  try {
    await client.loadSpec()
  } catch (error) {
    console.error('Error in k8s client', error)
  }

  const parseImageRegex = /^(\S+?\.\S+?\/|\S+?:\d+\/){0,1}(\S+?):(\S+?){0,1}(@\S+?){0,1}$/i
  function parseImage (image) {
    const imageObj = { name: image }
    const parsedImage = parseImageRegex.exec(image)
    if (parsedImage) {
      if (parsedImage.length > 3) {
        if (parsedImage[1]) {
          imageObj.registry = parsedImage[1]
        }
        imageObj.name = parsedImage[2]
        imageObj.tag = parsedImage[3]
        if (parsedImage[4] !== undefined) {
          imageObj.digest = parsedImage[4].substring(1, parsedImage[4].length)
        }
      }
    }
    return imageObj
  }

  // 1:name=systemd:/kubepods/besteffort/podff89dddf-c58b-4b31-a791-9ca5ec31b803/b50973d3d4fe9a1dbc2a13b9731749f220961cd2cc1040c7285516bb132d0f27
  const getId = data => {
    if (!data) {
      return null
    }

    let id = null
    // Break each line up
    const lines = data.split('\n')
    // look for the id
    lines.some(line => {
      if (!line.includes('name=systemd')) {
        return false
      }
      // split the line into an array on the '/'
      const path = line.split('/')
      // pop the last array item because it's the container id
      id = path.pop()
      if (id) {
        return true
      }
      return false
    })
    return id
  }

  const getContainerId = async () => {
    try {
      const cgroups = await readFileAsync(CGROUP_FILE, 'utf8')
      return getId(cgroups)
    } catch (e) {
      console.log(
        'cgroupContainerId > Error in kubernetes.js: ',
        e
      )
      return null
    }
  }

  const getPod = async ({ podNamespace, podName }) => {
    return await client.api.v1
      .namespaces(podNamespace)
      .pods(podName)
      .get()
  }

  try {
    // Ping Cgroups to get ContainerId
    const containerIdFromCgroup = await getContainerId()

    // Ping K8s API to get Pod data
    const res = await getPod({ podNamespace, podName })

    // Destructure response body object
    const {
      spec: { nodeName },
      status: { containerStatuses }
    } = res.body

    // Map container statuses into better structure
    const mappedContainerStatuses = containerStatuses
      .map(cs => {
        const obj = { container: {}, image: {} }
        obj.container.name = cs.name
        obj.image = parseImage(cs.image)
        obj.image.digest = cs.imageID
          .replace('docker-pullable://', '')
          .replace(obj.image.name, '')
          .replace('@', '')
        obj.container.id = cs.containerID.replace('docker://', '')
        return obj
      })

    // Filter out the container that the spm-agent is running in
    const filteredContainer = mappedContainerStatuses
      .filter(c => c.container.id === containerIdFromCgroup)
      .pop()

    if (!filteredContainer) {
      const kubernetesTags = {
        os: {
          host: nodeName
        },
        ...mappedContainerStatuses[0]
      }

      process.env.kubernetesTags = JSON.stringify(kubernetesTags)
      return
    }

    const kubernetesTags = {
      os: {
        host: nodeName
      },
      ...filteredContainer
    }
    process.env.kubernetesTags = JSON.stringify(kubernetesTags)
  } catch (error) {
    console.log('error in kubernetes.js: ', error)
  }
})()
<|endoftext|>


<commit_before>/* @flow */

import type {Manifest, FetchedMetadata} from '../../types.js';
import type PackageRequest from '../../package-request.js';
import TarballFetcher from '../../fetchers/tarball-fetcher.js';
import ExoticResolver from './exotic-resolver.js';
import Git from './git-resolver.js';
import * as versionUtil from '../../util/version.js';
import * as crypto from '../../util/crypto.js';
import * as fs from '../../util/fs.js';

const invariant = require('invariant');

export default class TarballResolver extends ExoticResolver {
  constructor(request: PackageRequest, fragment: string) {
    super(request, fragment);

    const {hash, url} = versionUtil.explodeHashedUrl(fragment);
    this.hash = hash;
    this.url = url;
  }

  url: string;
  hash: string;

  static isVersion(pattern: string): boolean {
    // we can sometimes match git urls which we don't want
    if (Git.isVersion(pattern)) {
      return false;
    }

    // full http url
    if (pattern.startsWith('http://') || pattern.startsWith('https://')) {
      return true;
    }

    // local file reference
    if (pattern.endsWith('.tgz') || pattern.endsWith('tar.gz')) {
      return true;
    }

    return false;
  }

  async resolve(): Promise<Manifest> {
    const shrunk = this.request.getLocked('tarball');
    if (shrunk) {
      return shrunk;
    }

    let {url, hash, registry} = this;
    let pkgJson;

    // generate temp directory
    const dest = this.config.getTemp(crypto.hash(url));

    if (await this.config.isValidModuleDest(dest)) {
      // load from local cache
      ({package: pkgJson, hash, registry} = await this.config.readPackageMetadata(dest));
    } else {
      // delete if invalid
      await fs.unlink(dest);

      const fetcher = new TarballFetcher(dest, {
        type: 'tarball',
        reference: url,
        registry,
        hash,
      }, this.config, false);

      // fetch file and get it's hash
      const fetched: FetchedMetadata = await fetcher.fetch();
      pkgJson = fetched.package;
      hash = fetched.hash;

      registry = pkgJson._registry;
      invariant(registry, 'expected registry');
    }

    // use the commit/tarball hash as the uid as we can't rely on the version as it's not
    // in the registry
    pkgJson._uid = hash;

    // set remote so it can be "fetched"
    pkgJson._remote = {
      type: 'copy',
      resolved: `${url}#${hash}`,
      hash,
      registry,
      reference: dest,
    };

    return pkgJson;
  }
}
<commit_msg>fix tarball resolution of named patterns with .tgz extension - fixes #455 (#509)<commit_after>/* @flow */

import type {Manifest, FetchedMetadata} from '../../types.js';
import type PackageRequest from '../../package-request.js';
import TarballFetcher from '../../fetchers/tarball-fetcher.js';
import ExoticResolver from './exotic-resolver.js';
import Git from './git-resolver.js';
import * as versionUtil from '../../util/version.js';
import * as crypto from '../../util/crypto.js';
import * as fs from '../../util/fs.js';

const invariant = require('invariant');

export default class TarballResolver extends ExoticResolver {
  constructor(request: PackageRequest, fragment: string) {
    super(request, fragment);

    const {hash, url} = versionUtil.explodeHashedUrl(fragment);
    this.hash = hash;
    this.url = url;
  }

  url: string;
  hash: string;

  static isVersion(pattern: string): boolean {
    // we can sometimes match git urls which we don't want
    if (Git.isVersion(pattern)) {
      return false;
    }

    // full http url
    if (pattern.startsWith('http://') || pattern.startsWith('https://')) {
      return true;
    }

    // local file reference - ignore patterns with names
    if (pattern.indexOf('@') < 0) {
      if (pattern.endsWith('.tgz') || pattern.endsWith('tar.gz')) {
        return true;
      }
    }

    return false;
  }

  async resolve(): Promise<Manifest> {
    const shrunk = this.request.getLocked('tarball');
    if (shrunk) {
      return shrunk;
    }

    let {url, hash, registry} = this;
    let pkgJson;

    // generate temp directory
    const dest = this.config.getTemp(crypto.hash(url));

    if (await this.config.isValidModuleDest(dest)) {
      // load from local cache
      ({package: pkgJson, hash, registry} = await this.config.readPackageMetadata(dest));
    } else {
      // delete if invalid
      await fs.unlink(dest);

      const fetcher = new TarballFetcher(dest, {
        type: 'tarball',
        reference: url,
        registry,
        hash,
      }, this.config, false);

      // fetch file and get it's hash
      const fetched: FetchedMetadata = await fetcher.fetch();
      pkgJson = fetched.package;
      hash = fetched.hash;

      registry = pkgJson._registry;
      invariant(registry, 'expected registry');
    }

    // use the commit/tarball hash as the uid as we can't rely on the version as it's not
    // in the registry
    pkgJson._uid = hash;

    // set remote so it can be "fetched"
    pkgJson._remote = {
      type: 'copy',
      resolved: `${url}#${hash}`,
      hash,
      registry,
      reference: dest,
    };

    return pkgJson;
  }
}
<|endoftext|>


<commit_before>var MAX_LENGTH = 512;
var MIN_LENGTH = 400;
var SIZE = 1024 * 2;
var COUNT = 100;
var count = 0;
var logs = [];

function getLogs(startTime, count) {
	var len = logs.length;
	if (!len || startTime == -1) {
		return [];
	}

	count = Math.min(count || COUNT, len);
	if (startTime == -2) {
		return logs.slice(0, count);
	}

	startTime = ((startTime || Date.now() - 3000) + '').split('-');

	startTime[0] = parseInt(startTime[0], 10) || 0;
	startTime[1] = parseInt(startTime[1], 10) || 0;

	if (compareId(logs[0].id, startTime)) {

		return logs.slice(0, count);
	}

	var end = len - 1;
	if (!end || !compareId(logs[end].id, startTime)) {
		return  [];
	}

	var index = getIndex(startTime, 0, end);
	return logs.slice(index, index + count);
}

function getIndex(startTime, start, end) {
	if (end - start <= 1) {
		return compareId(logs[start].id, startTime) ? start : end;
	}

	var mid = Math.floor((start + end) / 2);
	return compareId(logs[mid].id, startTime) ? getIndex(startTime, start, mid) : getIndex(startTime, mid + 1, end);
}

function compareId(curId, refId) {
	curId = curId.split('-');
	return curId[0] > refId[0] || (curId[0] == refId[0] && curId[1] > refId[1]);
}

module.exports = function init(proxy) {

	proxy.addLog = function set(log) {
		if (!log) {
			return;
		}

		var now = Date.now();
		var text = log.text;
		if (text == null) {
			text = '';
		} else if (typeof text != 'string') {
			text += '';
		}
		logs.push({
			id: now + '-' + ++count,
			date: now,
			level: /^fatal|error|warn|info|debug$/.test(log.level) ? log.level : 'info',
			text: text.substring(0, SIZE)
		});

		var len = logs.length;
		if (len > MAX_LENGTH) {
			logs = logs.slice(len - MIN_LENGTH, len);
		}
	};

	proxy.getLogs = getLogs;
};<commit_msg>refactor: The maximum length of the log text is adjusted to 32K<commit_after>var MAX_LENGTH = 512;
var MIN_LENGTH = 400;
var SIZE = 1024 * 32;
var COUNT = 100;
var count = 0;
var logs = [];

function getLogs(startTime, count) {
	var len = logs.length;
	if (!len || startTime == -1) {
		return [];
	}

	count = Math.min(count || COUNT, len);
	if (startTime == -2) {
		return logs.slice(0, count);
	}

	startTime = ((startTime || Date.now() - 3000) + '').split('-');

	startTime[0] = parseInt(startTime[0], 10) || 0;
	startTime[1] = parseInt(startTime[1], 10) || 0;

	if (compareId(logs[0].id, startTime)) {

		return logs.slice(0, count);
	}

	var end = len - 1;
	if (!end || !compareId(logs[end].id, startTime)) {
		return  [];
	}

	var index = getIndex(startTime, 0, end);
	return logs.slice(index, index + count);
}

function getIndex(startTime, start, end) {
	if (end - start <= 1) {
		return compareId(logs[start].id, startTime) ? start : end;
	}

	var mid = Math.floor((start + end) / 2);
	return compareId(logs[mid].id, startTime) ? getIndex(startTime, start, mid) : getIndex(startTime, mid + 1, end);
}

function compareId(curId, refId) {
	curId = curId.split('-');
	return curId[0] > refId[0] || (curId[0] == refId[0] && curId[1] > refId[1]);
}

module.exports = function init(proxy) {

	proxy.addLog = function set(log) {
		if (!log) {
			return;
		}

		var now = Date.now();
		var text = log.text;
		if (text == null) {
			text = '';
		} else if (typeof text != 'string') {
			text += '';
		}
		logs.push({
			id: now + '-' + ++count,
			date: now,
			level: /^fatal|error|warn|info|debug$/.test(log.level) ? log.level : 'info',
			text: text.substring(0, SIZE)
		});

		var len = logs.length;
		if (len > MAX_LENGTH) {
			logs = logs.slice(len - MIN_LENGTH, len);
		}
	};

	proxy.getLogs = getLogs;
};<|endoftext|>


<commit_before>var os = require('os');
var admin = require('pomelo-admin');
var utils = require('./utils');
var Constants = require('./constants');
var pathUtil = require('./pathUtil');
var starter = require('../master/starter');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var pro = module.exports;

/**
 * Load admin modules
 */
pro.loadModules = function(self, consoleService) {
  // load app register modules
  var _modules = self.app.get(Constants.KEYWORDS.MODULE);

  if(!_modules) {
    return;
  }

  var modules = [];
  for(var m in _modules){
    modules.push(_modules[m]);
  }

  var record, moduleId, module;
  for(var i=0, l=modules.length; i<l; i++) {
    record = modules[i];
    if(typeof record.module === 'function') {
      module = record.module(record.opts, consoleService);
    } else {
      module = record.module;
    }

    moduleId = record.moduleId || module.moduleId;

    if(!moduleId) {
      logger.warn('ignore an unknown module.');
      continue;
    }

    consoleService.register(moduleId, module);
    self.modules.push(module);
  }
};

pro.startModules = function(modules, cb) {
  // invoke the start lifecycle method of modules

  if(!modules) {
    return;
  }
  startModule(null, modules, 0, cb);
};

/**
 * Append the default system admin modules
 */
pro.registerDefaultModules = function(isMaster, app) {
  if(isMaster) {
    app.registerAdmin(require('../modules/masterwatcher'), {app: app});
  } else {
    app.registerAdmin(require('../modules/monitorwatcher'), {app: app});
  }

  app.registerAdmin(admin.modules.watchServer,{app:app});
  app.registerAdmin(require('../modules/console'), {app: app, starter: starter});
  if(app.enabled('systemMonitor')) {
    app.registerAdmin(admin.modules.systemInfo);
    app.registerAdmin(admin.modules.nodeInfo);
    app.registerAdmin(admin.modules.monitorLog, {path: pathUtil.getLogPath(app.getBase())});
    app.registerAdmin(admin.modules.scripts, {app:app, path: pathUtil.getScriptPath(app.getBase())});
    if(os.platform() !== 'win32') {
      app.registerAdmin(admin.modules.profiler);
    }
  }
};

var startModule = function(err, modules, index, cb) {
  if(err || index >= modules.length) {
    utils.invokeCallback(cb, err);
    return;
  }

  var module = modules[index];
  if(module && typeof module.start === 'function') {
    module.start(function(err) {
      startModule(err, modules, index + 1, cb);
    });
  } else {
    startModule(err, modules, index + 1, cb);
  }
};<commit_msg>update constants<commit_after>var os = require('os');
var admin = require('pomelo-admin');
var utils = require('./utils');
var Constants = require('./constants');
var pathUtil = require('./pathUtil');
var starter = require('../master/starter');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var pro = module.exports;

/**
 * Load admin modules
 */
pro.loadModules = function(self, consoleService) {
  // load app register modules
  var _modules = self.app.get(Constants.KEYWORDS.MODULE);

  if(!_modules) {
    return;
  }

  var modules = [];
  for(var m in _modules){
    modules.push(_modules[m]);
  }

  var record, moduleId, module;
  for(var i=0, l=modules.length; i<l; i++) {
    record = modules[i];
    if(typeof record.module === 'function') {
      module = record.module(record.opts, consoleService);
    } else {
      module = record.module;
    }

    moduleId = record.moduleId || module.moduleId;

    if(!moduleId) {
      logger.warn('ignore an unknown module.');
      continue;
    }

    consoleService.register(moduleId, module);
    self.modules.push(module);
  }
};

pro.startModules = function(modules, cb) {
  // invoke the start lifecycle method of modules

  if(!modules) {
    return;
  }
  startModule(null, modules, 0, cb);
};

/**
 * Append the default system admin modules
 */
pro.registerDefaultModules = function(isMaster, app) {
  if(isMaster) {
    app.registerAdmin(require('../modules/masterwatcher'), {app: app});
  } else {
    app.registerAdmin(require('../modules/monitorwatcher'), {app: app});
  }

  app.registerAdmin(admin.modules.watchServer,{app:app});
  app.registerAdmin(require('../modules/console'), {app: app, starter: starter});
  if(app.enabled('systemMonitor')) {
    app.registerAdmin(admin.modules.systemInfo);
    app.registerAdmin(admin.modules.nodeInfo);
    app.registerAdmin(admin.modules.monitorLog, {path: pathUtil.getLogPath(app.getBase())});
    app.registerAdmin(admin.modules.scripts, {app:app, path: pathUtil.getScriptPath(app.getBase())});
    if(os.platform() !== Constants.PLATFORM.WIN) {
      app.registerAdmin(admin.modules.profiler);
    }
  }
};

var startModule = function(err, modules, index, cb) {
  if(err || index >= modules.length) {
    utils.invokeCallback(cb, err);
    return;
  }

  var module = modules[index];
  if(module && typeof module.start === 'function') {
    module.start(function(err) {
      startModule(err, modules, index + 1, cb);
    });
  } else {
    startModule(err, modules, index + 1, cb);
  }
};<|endoftext|>


<commit_before>/**
 * Yobi, Project Hosting SW
 *
 * Copyright 2013 NAVER Corp.
 * http://yobi.io
 *
 * @Author Suwon Chae
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
yobi.Mention = function(htOptions) {

    var htVar = {};
    var htElement = {};

    /**
     * Initialize
     *
     * @param {Hash Table} htOptions
     */
    function _init(htOptions){
        _initVar(htOptions);
        _initElement();
        _attachEvent();
    }

    /**
     * Initialize Variables
     *
     * @param {Hash Table} htOptions
     */
    function _initVar(htOptions) {
        htVar = htOptions || {}; // set htVar as htOptions
        htVar.atConfig = {
            at: "@",
            limit: 10,
            data: [],
            tpl: "
 ${username} ${loginid}
",
            show_the_at: true
        }
        htVar.nKeyupEventGenerator = null;
        htVar.sMentionText = null;
    }

    /**
     * Initialize Element variables
     */
    function _initElement() {
        if (!htVar.target) {
            if (window.console) {
                console.error("mention form element targeting doesn't exist!")
            }
            return;
        }
        htElement.welTarget = $(htVar.target);
    }
    /**
     * attachEvent
     */
    function _attachEvent() {
        htElement.welTarget.on("keypress", _onKeyInput);
        if (jQuery.browser.mozilla){
            htElement.welTarget.on("focus", _startKeyupEventGenerator);
            htElement.welTarget.on("blur", _stopKeyupEventGenerator);
        }
    }

    /**
     * Event handler on KeyInput event
     *
     * @param {Event} eEvt
     */
    function _onKeyInput(eEvt){
        eEvt = eEvt || window.event;

        var charCode = eEvt.which || eEvt.keyCode;
        if(charCode === 64 || charCode === 35) { // 64 = @, 35 = #
            if(htVar.atConfig.data.length == 0) {
                _findMentionList();
            }
        }
    }

    function _startKeyupEventGenerator(){
        if (htVar.nKeyupEventGenerator){
            clearInterval(htVar.nKeyupEventGenerator);
        }

        htVar.nKeyupEventGenerator = setInterval(
            function(){
                if (htVar.sMentionText != htElement.welTarget.val()){
                    htElement.welTarget.trigger("keyup");
                    htVar.sMentionText = htElement.welTarget.val();
                }
            }
            ,100);
    }

    function _stopKeyupEventGenerator(){
        if (htVar.nKeyupEventGenerator){
            clearInterval(htVar.nKeyupEventGenerator);
            htVar.nKeyupEventGenerator = null;
        }
    }

    /**
     * Find Userlist
     */
    function _findMentionList(){
        $.ajax({
            "url"        : htVar.url,
            "type"       : "get",
            "contentType": "application/json",
            "dataType"   : "json"
        }).done(_onLoadUserList);
    }

    function _onLoadUserList(aData){
        htVar.atConfig.data = aData.result;

        $inputor = htElement.welTarget
            .atwho(htVar.atConfig)
            .atwho({
                at: "#",
                limit: 10,
                tpl: '
#${issueNo} ${title}
',
                data: aData.issues
            });

        $inputor.atwho("run");
    }

    _init(htOptions || {});
};
<commit_msg>customize mention list sorting<commit_after>/**
 * Yobi, Project Hosting SW
 *
 * Copyright 2013 NAVER Corp.
 * http://yobi.io
 *
 * @Author Suwon Chae
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
yobi.Mention = function(htOptions) {

    var htVar = {};
    var htElement = {};

    /**
     * Initialize
     *
     * @param {Hash Table} htOptions
     */
    function _init(htOptions){
        _initVar(htOptions);
        _initElement();
        _attachEvent();
    }

    /**
     * Initialize Variables
     *
     * @param {Hash Table} htOptions
     */
    function _initVar(htOptions) {
        htVar = htOptions || {}; // set htVar as htOptions
        htVar.doesNotDataLoaded = true;
        htVar.nKeyupEventGenerator = null;
        htVar.sMentionText = null;
    }

    /**
     * Initialize Element variables
     */
    function _initElement() {
        if (!htVar.target) {
            if (window.console) {
                console.error("mention form element targeting doesn't exist!")
            }
            return;
        }
        htElement.welTarget = $(htVar.target);
    }
    /**
     * attachEvent
     */
    function _attachEvent() {
        htElement.welTarget.on("keypress", _onKeyInput);
        if (jQuery.browser.mozilla){
            htElement.welTarget.on("focus", _startKeyupEventGenerator);
            htElement.welTarget.on("blur", _stopKeyupEventGenerator);
        }
    }

    /**
     * Event handler on KeyInput event
     *
     * @param {Event} eEvt
     */
    function _onKeyInput(eEvt){
        eEvt = eEvt || window.event;

        var charCode = eEvt.which || eEvt.keyCode;
        if(charCode === 64 || charCode === 35) { // 64 = @, 35 = #
            if(htVar.doesNotDataLoaded) {
                _findMentionList();
            }
        }
    }

    function _startKeyupEventGenerator(){
        if (htVar.nKeyupEventGenerator){
            clearInterval(htVar.nKeyupEventGenerator);
        }

        htVar.nKeyupEventGenerator = setInterval(
            function(){
                if (htVar.sMentionText != htElement.welTarget.val()){
                    htElement.welTarget.trigger("keyup");
                    htVar.sMentionText = htElement.welTarget.val();
                }
            }
            ,100);
    }

    function _stopKeyupEventGenerator(){
        if (htVar.nKeyupEventGenerator){
            clearInterval(htVar.nKeyupEventGenerator);
            htVar.nKeyupEventGenerator = null;
        }
    }

    /**
     * Find Userlist
     */
    function _findMentionList(){
        $.ajax({
            "url"        : htVar.url,
            "type"       : "get",
            "contentType": "application/json",
            "dataType"   : "json"
        }).done(_onLoadUserList);
    }

    function _onLoadUserList(aData){
        htVar.doesNotDataLoaded = false;

        htElement.welTarget
            .atwho({
                at: "@",
                limit: 10,
                data: aData.result,
                tpl: "
 ${username} ${loginid}
",
                show_the_at: true
            })
            .atwho({
                at: "#",
                limit: 10,
                tpl: '
#${issueNo} ${title}
',
                data: aData.issues,
                callbacks: {
                    sorter: function(query, items, searchKey) {
                        var item, i, len, results;
                        if (!query) {
                            return items;
                        }
                        results = [];
                        for (i = 0, len = items.length; i < len; i++) {
                            item = items[i];

                            if (item.issueNo === query) {
                                item.atwhoOrder = 0;
                            } else {
                                var issueNoIndexOf = item.issueNo.toLowerCase().indexOf(query.toLowerCase());
                                item.atwhoOrder = i + 1
                                    + Math.pow(10, issueNoIndexOf)
                                    + ((issueNoIndexOf > -1) ? 0 : Math.pow(100, item.title.toLowerCase().indexOf(query.toLowerCase())));
                            }
                            results.push(item);
                        }
                        return results.sort(function(a, b) {
                            return a.atwhoOrder - b.atwhoOrder;
                        });
                    }
                }
            })
            .atwho("run");
    }

    _init(htOptions || {});
};
<|endoftext|>


<commit_before>
module.exports = exports;

var fs = require('fs')
  , tar = require('tar')
  , path = require('path')
  , zlib = require('zlib')
  , log = require('npmlog')
  , semver = require('semver')
  , request = require('request')
  , win = process.platform == 'win32'
  , os = require('os')
  , url = require('url')
  , existsAsync = fs.exists || path.exists
  , cp = require('child_process')

function eval_template(template,opts) {
    Object.keys(opts).forEach(function(key) {
        var pattern = '{'+key+'}';
        while (template.indexOf(pattern) > -1) {
            template = template.replace(pattern,opts[key]);
        }
    });
    return template;
}

function get_node_abi(runtime) {
    // process.versions.modules added in >= v0.10.4 and v0.11.7
    // https://github.com/joyent/node/commit/ccabd4a6fa8a6eb79d29bc3bbe9fe2b6531c2d8e
    return process.versions.modules ? runtime+'-v' + (+process.versions.modules) :
           'v8-' + process.versions.v8.split('.').slice(0,2).join('.');
}

function validate_config(package_json,callback) {
    var msg = package_json.name + ' package.json is not node-pre-gyp ready:\n';
    if (!package_json.main) {
        return callback(new Error(msg+"package.json must declare 'main'"));
    }
    if (!package_json.binary) {
        return callback(new Error(msg+"package.json must declare 'binary' property.\nSee https://github.com/springmeyer/node-pre-gyp#design for details\n"));
    }
    var o = package_json.binary;
    if (!o.module_path) {
        return callback(new Error(msg+"package.json must declare 'binary.module_path'"));
    }
    if (!o.module_name) {
        return callback(new Error(msg+"package.json must declare 'binary.module_name'"));
    }
    if (!o.template) {
        return callback(new Error(msg+"package.json must declare 'binary.template'"));
    }
    if (!o.remote_uri) {
        return callback(new Error(msg+"package.json must declare 'binary.remote_uri'"));
    }
    // enforce `remote_uri` as https
    var protocol = url.parse(o.remote_uri).protocol;
    if (protocol !== 'https:') {
        return callback(new Error("'remote_uri' protocol ("+protocol+") is invalid - only 'https:' is accepted"));
    }
    return callback();
};

module.exports.validate_config = validate_config;

module.exports.evaluate = function(package_json,options,callback) {
    validate_config(package_json,function(err) {
        if (err) return callback(err);
        var v = package_json.version;
        var module_version = semver.parse(v);
        var runtime = options.runtime || 'node';
        var opts = {
            configuration: (options.debug === true) ? 'Debug' : 'Release'
            , module_name: package_json.binary.module_name
            , version: module_version.version
            , prerelease: module_version.prerelease.length ? v.slice(v.indexOf(module_version.prerelease[0])) : ''
            , major: module_version.major
            , minor: module_version.minor
            , patch: module_version.patch
            , runtime: runtime
            , node_abi: get_node_abi(runtime)
            , platform: process.platform
            , arch: process.arch
            , target_arch: options.target_arch || process.arch
            , module_main: package_json.main
        }
        opts.versioned = eval_template(package_json.binary.template,opts);
        return callback(null,opts);
    });
}
<commit_msg>allow options to override platform (target_platform) and arch (target_arch) - closes #11<commit_after>
module.exports = exports;

var fs = require('fs')
  , tar = require('tar')
  , path = require('path')
  , zlib = require('zlib')
  , log = require('npmlog')
  , semver = require('semver')
  , request = require('request')
  , win = process.platform == 'win32'
  , os = require('os')
  , url = require('url')
  , existsAsync = fs.exists || path.exists
  , cp = require('child_process')

function eval_template(template,opts) {
    Object.keys(opts).forEach(function(key) {
        var pattern = '{'+key+'}';
        while (template.indexOf(pattern) > -1) {
            template = template.replace(pattern,opts[key]);
        }
    });
    return template;
}

function get_node_abi(runtime) {
    // process.versions.modules added in >= v0.10.4 and v0.11.7
    // https://github.com/joyent/node/commit/ccabd4a6fa8a6eb79d29bc3bbe9fe2b6531c2d8e
    return process.versions.modules ? runtime+'-v' + (+process.versions.modules) :
           'v8-' + process.versions.v8.split('.').slice(0,2).join('.');
}

function validate_config(package_json,callback) {
    var msg = package_json.name + ' package.json is not node-pre-gyp ready:\n';
    if (!package_json.main) {
        return callback(new Error(msg+"package.json must declare 'main'"));
    }
    if (!package_json.binary) {
        return callback(new Error(msg+"package.json must declare 'binary' property.\nSee https://github.com/springmeyer/node-pre-gyp#design for details\n"));
    }
    var o = package_json.binary;
    if (!o.module_path) {
        return callback(new Error(msg+"package.json must declare 'binary.module_path'"));
    }
    if (!o.module_name) {
        return callback(new Error(msg+"package.json must declare 'binary.module_name'"));
    }
    if (!o.template) {
        return callback(new Error(msg+"package.json must declare 'binary.template'"));
    }
    if (!o.remote_uri) {
        return callback(new Error(msg+"package.json must declare 'binary.remote_uri'"));
    }
    // enforce `remote_uri` as https
    var protocol = url.parse(o.remote_uri).protocol;
    if (protocol !== 'https:') {
        return callback(new Error("'remote_uri' protocol ("+protocol+") is invalid - only 'https:' is accepted"));
    }
    return callback();
};

module.exports.validate_config = validate_config;

module.exports.evaluate = function(package_json,options,callback) {
    validate_config(package_json,function(err) {
        if (err) return callback(err);
        var v = package_json.version;
        var module_version = semver.parse(v);
        var runtime = options.runtime || 'node';
        var opts = {
            configuration: (options.debug === true) ? 'Debug' : 'Release'
            , module_name: package_json.binary.module_name
            , version: module_version.version
            , prerelease: module_version.prerelease.length ? v.slice(v.indexOf(module_version.prerelease[0])) : ''
            , major: module_version.major
            , minor: module_version.minor
            , patch: module_version.patch
            , runtime: runtime
            , node_abi: get_node_abi(runtime)
            , platform: options.target_platform || process.platform
            , arch: options.target_arch || process.arch
            , target_arch: options.target_arch || process.arch
            , module_main: package_json.main
        }
        opts.versioned = eval_template(package_json.binary.template,opts);
        return callback(null,opts);
    });
}
<|endoftext|>