<commit_before>module('lively.ide.SystemCodeBrowser').requires('lively.ide.BrowserFramework', 'lively.ide.SystemBrowserNodes', 'lively.ide.BrowserCommands', 'lively.ide.SourceDatabase', 'lively.ide.SyntaxHighlighting').toRun(function() {

// ===========================================================================
// Browsing js files and OMeta
// ===========================================================================
lively.ide.BasicBrowser.subclass('lively.ide.SystemBrowser',
'settings', {
    documentation: 'Browser for source code parsed from js files',
    viewTitle: "SystemBrowser",
    isSystemBrowser: true
},
'initializing', {
    initialize: function($super) {
        $super();
        this.evaluate = true;
        this.targetURL = null;
        this.installDefaultFilters();
    },
    onrestore: function() {
        if (this.panel) this.onDeserialize.bind(this).delay(0);
    },
    onDeserialize: function() {
        var position = this.panel.getPosition(),
            extent = this.panel.getExtent(),
            newBrowser = new this.constructor();

        var targetURL = this.targetURL;
        if (targetURL) {
            if (!URL.root.eqDomain(targetURL)) targetURL = URL.codeBase;
            newBrowser.targetURL = targetURL;
        }

        if (this.isNavigationCollapsed) {
            var fullExtent;
            if (!this.view.isCollapsed()) {
                fullExtent = this.view.getExtent().addPt(lively.pt(0, this.navigationHeight()));
                this.view.setExtent(fullExtent);
                this.sourceOnlyPanel.remove();
            } else {
                fullExtent = this.view.expandedExtent.addPt(lively.pt(0, this.navigationHeight()));
                this.view.expandedExtent = fullExtent;
            }
        }

        newBrowser.buildView(extent);
        newBrowser.view = this.view;

        this.view.targetMorph = newBrowser.panel;

        if (!this.view.isCollapsed()) {
            this.view.addMorph(newBrowser.panel);
        }
        newBrowser.panel.setPosition(position);
        this.panel.remove();

        // FIXME: selectNode doesn't work
        newBrowser.selectNode(this.selectedNode());

        if (this.isNavigationCollapsed && !this.view.isCollapsed()) {
            newBrowser.toggleCollapseNavigation();
        }
    },
    setupLocationInput: function($super) {
        $super();
        var locInput = this.locationInput();
        if (!locInput) return;

        connect(this, 'targetURL', this, 'setLocationInputFromURL');
        connect(this.locationInput(), 'savedTextString', this, 'setTargetUrlFromString');
        this.locationInput().applyStyle({fontSize: 8, textColor: Color.darkGray, borderWidth: 0});

        var button = this.panel.locationPaneMenuButton;
        button.setLabel('...');
        button.label.setPosition(button.label.getPosition().subPt(pt(0, 2)));
        connect(button, 'fire', this, 'openLocationPaneMenu');
    },
    openLocationPaneMenu: function() {
        var button = this.panel.locationPaneMenuButton;
        var self = this;
        var items = [
            ['Your directory', function() {self.switchToLocalCodebase()}],
            ['Lively code base', function() {self.switchToLivelyCodebase()}]];
        var menu = new lively.morphic.Menu('Location ...', items);
        var menuBtnTopLeft = button.bounds().topLeft();
        var menuTopLeft = menuBtnTopLeft.subPt(pt(menu.bounds().width, 0));

        menu.openIn(
            lively.morphic.World.current(),
            button.getGlobalTransform().transformPoint(pt(0-menu.bounds().width, 0)),
            false);
    },

    switchToLocalCodebase: function() {
        this.setTargetURL(
            $world.getUserName() ?
                $world.getUserDir() :
                URL.source.getDirectory());
    },
    start: function($super) {
        $super();
        this.mySourceControl().registerBrowser(this);
    },
    stop: function($super) {
        $super();
        this.mySourceControl().unregisterBrowser(this);
    },

    switchToLivelyCodebase: function() {
        this.setTargetURL(URL.codeBase.withFilename('lively/'));
    },

    setLocationInputFromURL: function(targetUrl) {
        var codeBaseString = this.codeBaseUrlString();
        var targetString = String(targetUrl);
        var locationInputString = targetString.replace(codeBaseString, '');
        this.locationInput().setTextString(locationInputString);
    },
    setTargetUrlFromString: function(aString) {
        var isCompleteUrlString = aString.startsWith('http://');
        if (isCompleteUrlString) {
            return this.setTargetURL(new URL(aString));
        }

        var isModuleSyntax = aString.indexOf('.') > -1; // e.g. 'lively.ide.SystemCodeBrowser.js'
        if (isModuleSyntax) {
            var module = lively.module(aString);
            this.browse(module.name());
            return;
        }
        var targetUrlString = String(this.codeBaseUrlString()).concat(aString);
        this.setTargetURL(new URL(targetUrlString))
    },
    openIn: function($super, world, position, extent, optTargetURL) {
        var lastOpened = lively.ide.sourceDB().registeredBrowsers.last();
        var win = $super(world, position, extent);
        var url = optTargetURL || (lastOpened && lastOpened.getTargetURL && lastOpened.getTargetURL());
        url && this.setTargetURL(url);
        return win;
    },
    codeBaseUrlString: function() {
        return String(this.sourceDatabase().codeBaseURL).replace('core/', '');
    },
    installDefaultFilters: function() {
        var sortFilter = (new lively.ide.SortCommand(this)).filter,
            nodeTypeFilter = lively.ide.NodeTypeFilter.defaultInstance();

        this.installFilterInPane(nodeTypeFilter, 'Pane1');
        this.installFilter(sortFilter);
    }

},
'accessing', {
    getTargetURL: function() {
        if (!this.targetURL) this.targetURL = this.sourceDatabase().codeBaseURL;
        return this.targetURL;
    },

    setTargetURL: function(urlOrString) {
        var url = urlOrString || URL.root;
        if (Object.isString(urlOrString) ) {
            try {
                if (urlOrString.startsWith('http://')) {
                    url = new URL(urlOrString);
                } else {
                    var module = lively.module(urlOrString);
                    lively.ide.browse(null, null, {name: module.name()}, this);
                    return;
                }
            } catch(e) {
                console.warn('SCB>>setTargetURL:' + e);
            }
        }
        url = url.withRelativePartsResolved();
        this.selectNothing();
        this.ensureSourceNotAccidentlyDeleted(function() {
            var prevURL = this.targetURL;
            // if (url.isLeaf())
            //     url = new URL(url.toString() + '/');
            try {
                this.targetURL = url;
                this.rootNode().locationChanged();
                this.allChanged();
            } catch(e) {
                show('couldn\'t set new URL ' + url + ' because ' + e);
                this.targetURL = prevURL;
                this.locationInput().setTextString(prevURL.toString());
                return
            }
            this.panel.targetURL = url; // FIXME for persistence
            console.log('new url: ' + url);
        });
    },

    setLocation: function() {},

    rootNode: function() {
        var srcCtrl = lively.ide.startSourceControl();
        if (!this._rootNode)
            this._rootNode = new lively.ide.SourceControlNode(srcCtrl, this, null);
        return this._rootNode;
    },

    commands: function() {
        // lively.ide.BrowserCommand.allSubclasses().collect(function(ea) { return ea.type}).join(',\n')
        return [
            // lively.ide.BrowseWorldCommand,
            lively.ide.AddNewFileCommand,
            lively.ide.AllModulesLoadCommand,
            lively.ide.ShowLineNumbersCommand,
            // lively.ide.RefreshCommand,
            lively.ide.ParserDebugCommand,
            lively.ide.EvaluateCommand,
            lively.ide.SortCommand,
            lively.ide.ViewSourceCommand,
            lively.ide.ClassHierarchyViewCommand,
            lively.ide.UnloadClassCommand,
            lively.ide.AddClassToFileFragmentCommand,
            lively.ide.AddObjectExtendToFileFragmentCommand,
            lively.ide.AddLayerToFileFragmentCommand,
            lively.ide.AddMethodToFileFragmentCommand,
            lively.ide.RunTestMethodCommand,
            lively.ide.OpenInFileEditorCommand,
            lively.ide.OpenVersionsOfFile,
            lively.ide.OpenModulePartCommand]
    },

    sourceDatabase: function() { return this.rootNode().target; }
},
'browser actions', {
    getSelectedModule: function() {
        var currentModule, moduleName = this.currentModuleName;
        if (!moduleName || moduleName.include('undefined')) return null;
        return lively.lookup(lively.ide.sourceDB().addModule(moduleName).moduleName());
    },

    withCurrentModuleActiveDo: function(func) {
        var currentModule = this.getSelectedModule();
        try {
            currentModule && currentModule.activate();
            return func.call(this);
        } catch (er) {
            throw(er);
        } finally {
            currentModule && currentModule.deactivate();
        }
    },
    browse: function(moduleName, klassName, methodName) {
        var targetModule = module(moduleName),
            moduleURL = new URL(targetModule.uri()),
            dir = moduleURL.getDirectory(),
            fileName  = moduleURL.filename();

        var srcCtrl = lively.ide.startSourceControl();
        srcCtrl.addModule(targetModule.relativePath());

        this.setTargetURL(dir);
        fileName && this.inPaneSelectNodeNamed('Pane1', fileName);
        klassName && this.inPaneSelectNodeNamed('Pane2', klassName);
        methodName && this.inPaneSelectNodeNamed('Pane4', methodName);
    },
});

Object.extend(lively.ide.SystemBrowser, {
    // lively.ide.SystemBrowser.browse('lively.Examples')
    browse: function(moduleName, klassName, methodName) {
        var browser = new lively.ide.SystemBrowser();
        browser.openIn(lively.morphic.World.current());
        browser.browse(moduleName, klassName, methodName);
        return browser;
    },
});

Object.extend(lively.ide, {

    browse: function(/*args*/) {
        // args can be:
        // 1. objectName, methodName, moduleNameOrSpec, optional browser instance
        //   Browse a method in a object (class, layer, etc)
        //   See MethodFinder for original implementation
        //   Example:
        //   objectName = "lively.morphic.Morph"
        //   methodName = "onMouseDown"
        //   moduleNameOrSpec = "lively.morphic.Events"
        //     || {name: "lively.ast.LivelyJSParser", type: 'ometa'};
        // 2. URL (URL object or string), optional browser instance
        // 3. path (String) relative to URL.root, optional browser instance

        var args = Array.from(arguments);
        if (args.length === 0) { this.openSystemCodeBrowser(); return }
        if (args.length <= 2) { // url or path or options object
            var url = args[0].toString().startsWith('http:') ?
                new URL(args[0]) : URL.root.withFilename(args[0]);
            this.browseURL(url, args[1]/*optional browser*/);
        } else {
            var objectName = args[0],
                methodName = args[1],
                moduleNameOrSpec = args[2],
                browser = args[3];

            var promise = {}, moduleName, moduleType;
            if (Object.isString(moduleNameOrSpec)) {
                moduleName = moduleNameOrSpec;
            } else if (moduleNameOrSpec.name) {
                moduleName = moduleNameOrSpec.name;
                moduleType = moduleNameOrSpec.type || moduleType;
            }

            if (objectName) {
                objectName = objectName.replace(/^Global\./,"");
            }

            if (moduleName === 'Global') {
                alert("could not browse " + methodName + " in " + objectName + "\nb/c its sourceModule is \"Global\"");
                rootNode.browseIt({browser: browser});
                return false;
            }

            var relative = module(moduleName).relativePath(moduleType),
                moduleNode = lively.ide.startSourceControl().addModule(relative),
                rootNode = moduleNode.ast(),
                fileFragments = rootNode.subElements(10).select(function(ea) {
                    var path = ea.getOwnerNamePath();
                    return path.include(objectName) && (!methodName || path.include(methodName));
                });

            if (fileFragments.length > 0) {
                return fileFragments[0].browseIt({browser: browser});
            } else {
                alert("could not browse " + methodName + " in " + objectName);
                rootNode.browseIt({browser: browser});
                return false;
            }

            return promise;
        }
    },

    browseURL: function(url, browser) {
        browser = browser || this.openSystemCodeBrowser();
        if (url.isLeaf()) {
            var dir = url.getDirectory(),
                fileName = url.filename();
            browser.setTargetURL(dir);
            browser.selectNodeMatching(function(node) {
                return node && node.url && node.url().filename() == fileName;
            })
        } else {
            browser.setTargetURL(url);
        }
        browser.panel.getWindow().comeForward();
        return browser;
    },

    openSystemCodeBrowser: function() {
        var browser = new lively.ide.SystemBrowser();
        browser.openIn(lively.morphic.World.current());
        return browser;
    }

});

}) // end of module
<commit_msg>ide: fixing browseURL<commit_after>module('lively.ide.SystemCodeBrowser').requires('lively.ide.BrowserFramework', 'lively.ide.SystemBrowserNodes', 'lively.ide.BrowserCommands', 'lively.ide.SourceDatabase', 'lively.ide.SyntaxHighlighting').toRun(function() {

// ===========================================================================
// Browsing js files and OMeta
// ===========================================================================
lively.ide.BasicBrowser.subclass('lively.ide.SystemBrowser',
'settings', {
    documentation: 'Browser for source code parsed from js files',
    viewTitle: "SystemBrowser",
    isSystemBrowser: true
},
'initializing', {
    initialize: function($super) {
        $super();
        this.evaluate = true;
        this.targetURL = null;
        this.installDefaultFilters();
    },
    onrestore: function() {
        if (this.panel) this.onDeserialize.bind(this).delay(0);
    },
    onDeserialize: function() {
        var position = this.panel.getPosition(),
            extent = this.panel.getExtent(),
            newBrowser = new this.constructor();

        var targetURL = this.targetURL;
        if (targetURL) {
            if (!URL.root.eqDomain(targetURL)) targetURL = URL.codeBase;
            newBrowser.targetURL = targetURL;
        }

        if (this.isNavigationCollapsed) {
            var fullExtent;
            if (!this.view.isCollapsed()) {
                fullExtent = this.view.getExtent().addPt(lively.pt(0, this.navigationHeight()));
                this.view.setExtent(fullExtent);
                this.sourceOnlyPanel.remove();
            } else {
                fullExtent = this.view.expandedExtent.addPt(lively.pt(0, this.navigationHeight()));
                this.view.expandedExtent = fullExtent;
            }
        }

        newBrowser.buildView(extent);
        newBrowser.view = this.view;

        this.view.targetMorph = newBrowser.panel;

        if (!this.view.isCollapsed()) {
            this.view.addMorph(newBrowser.panel);
        }
        newBrowser.panel.setPosition(position);
        this.panel.remove();

        // FIXME: selectNode doesn't work
        newBrowser.selectNode(this.selectedNode());

        if (this.isNavigationCollapsed && !this.view.isCollapsed()) {
            newBrowser.toggleCollapseNavigation();
        }
    },
    setupLocationInput: function($super) {
        $super();
        var locInput = this.locationInput();
        if (!locInput) return;

        connect(this, 'targetURL', this, 'setLocationInputFromURL');
        connect(this.locationInput(), 'savedTextString', this, 'setTargetUrlFromString');
        this.locationInput().applyStyle({fontSize: 8, textColor: Color.darkGray, borderWidth: 0});

        var button = this.panel.locationPaneMenuButton;
        button.setLabel('...');
        button.label.setPosition(button.label.getPosition().subPt(pt(0, 2)));
        connect(button, 'fire', this, 'openLocationPaneMenu');
    },
    openLocationPaneMenu: function() {
        var button = this.panel.locationPaneMenuButton;
        var self = this;
        var items = [
            ['Your directory', function() {self.switchToLocalCodebase()}],
            ['Lively code base', function() {self.switchToLivelyCodebase()}]];
        var menu = new lively.morphic.Menu('Location ...', items);
        var menuBtnTopLeft = button.bounds().topLeft();
        var menuTopLeft = menuBtnTopLeft.subPt(pt(menu.bounds().width, 0));

        menu.openIn(
            lively.morphic.World.current(),
            button.getGlobalTransform().transformPoint(pt(0-menu.bounds().width, 0)),
            false);
    },

    switchToLocalCodebase: function() {
        this.setTargetURL(
            $world.getUserName() ?
                $world.getUserDir() :
                URL.source.getDirectory());
    },
    start: function($super) {
        $super();
        this.mySourceControl().registerBrowser(this);
    },
    stop: function($super) {
        $super();
        this.mySourceControl().unregisterBrowser(this);
    },

    switchToLivelyCodebase: function() {
        this.setTargetURL(URL.codeBase.withFilename('lively/'));
    },

    setLocationInputFromURL: function(targetUrl) {
        var codeBaseString = this.codeBaseUrlString();
        var targetString = String(targetUrl);
        var locationInputString = targetString.replace(codeBaseString, '');
        this.locationInput().setTextString(locationInputString);
    },
    setTargetUrlFromString: function(aString) {
        var isCompleteUrlString = aString.startsWith('http://');
        if (isCompleteUrlString) {
            return this.setTargetURL(new URL(aString));
        }

        var isModuleSyntax = aString.indexOf('.') > -1; // e.g. 'lively.ide.SystemCodeBrowser.js'
        if (isModuleSyntax) {
            var module = lively.module(aString);
            this.browse(module.name());
            return;
        }
        var targetUrlString = String(this.codeBaseUrlString()).concat(aString);
        this.setTargetURL(new URL(targetUrlString))
    },
    openIn: function($super, world, position, extent, optTargetURL) {
        var lastOpened = lively.ide.sourceDB().registeredBrowsers.last();
        var win = $super(world, position, extent);
        var url = optTargetURL || (lastOpened && lastOpened.getTargetURL && lastOpened.getTargetURL());
        url && this.setTargetURL(url);
        return win;
    },
    codeBaseUrlString: function() {
        return String(this.sourceDatabase().codeBaseURL).replace('core/', '');
    },
    installDefaultFilters: function() {
        var sortFilter = (new lively.ide.SortCommand(this)).filter,
            nodeTypeFilter = lively.ide.NodeTypeFilter.defaultInstance();

        this.installFilterInPane(nodeTypeFilter, 'Pane1');
        this.installFilter(sortFilter);
    }

},
'accessing', {
    getTargetURL: function() {
        if (!this.targetURL) this.targetURL = this.sourceDatabase().codeBaseURL;
        return this.targetURL;
    },

    setTargetURL: function(urlOrString) {
        var url = urlOrString || URL.root;
        if (Object.isString(urlOrString) ) {
            try {
                if (urlOrString.startsWith('http://')) {
                    url = new URL(urlOrString);
                } else {
                    var module = lively.module(urlOrString);
                    lively.ide.browse(null, null, {name: module.name()}, this);
                    return;
                }
            } catch(e) {
                console.warn('SCB>>setTargetURL:' + e);
            }
        }
        url = url.withRelativePartsResolved();
        this.selectNothing();
        this.ensureSourceNotAccidentlyDeleted(function() {
            var prevURL = this.targetURL;
            // if (url.isLeaf())
            //     url = new URL(url.toString() + '/');
            try {
                this.targetURL = url;
                this.rootNode().locationChanged();
                this.allChanged();
            } catch(e) {
                show('couldn\'t set new URL ' + url + ' because ' + e);
                this.targetURL = prevURL;
                this.locationInput().setTextString(prevURL.toString());
                return
            }
            this.panel.targetURL = url; // FIXME for persistence
            console.log('new url: ' + url);
        });
    },

    setLocation: function() {},

    rootNode: function() {
        var srcCtrl = lively.ide.startSourceControl();
        if (!this._rootNode)
            this._rootNode = new lively.ide.SourceControlNode(srcCtrl, this, null);
        return this._rootNode;
    },

    commands: function() {
        // lively.ide.BrowserCommand.allSubclasses().collect(function(ea) { return ea.type}).join(',\n')
        return [
            // lively.ide.BrowseWorldCommand,
            lively.ide.AddNewFileCommand,
            lively.ide.AllModulesLoadCommand,
            lively.ide.ShowLineNumbersCommand,
            // lively.ide.RefreshCommand,
            lively.ide.ParserDebugCommand,
            lively.ide.EvaluateCommand,
            lively.ide.SortCommand,
            lively.ide.ViewSourceCommand,
            lively.ide.ClassHierarchyViewCommand,
            lively.ide.UnloadClassCommand,
            lively.ide.AddClassToFileFragmentCommand,
            lively.ide.AddObjectExtendToFileFragmentCommand,
            lively.ide.AddLayerToFileFragmentCommand,
            lively.ide.AddMethodToFileFragmentCommand,
            lively.ide.RunTestMethodCommand,
            lively.ide.OpenInFileEditorCommand,
            lively.ide.OpenVersionsOfFile,
            lively.ide.OpenModulePartCommand]
    },

    sourceDatabase: function() { return this.rootNode().target; }
},
'browser actions', {
    getSelectedModule: function() {
        var currentModule, moduleName = this.currentModuleName;
        if (!moduleName || moduleName.include('undefined')) return null;
        return lively.lookup(lively.ide.sourceDB().addModule(moduleName).moduleName());
    },

    withCurrentModuleActiveDo: function(func) {
        var currentModule = this.getSelectedModule();
        try {
            currentModule && currentModule.activate();
            return func.call(this);
        } catch (er) {
            throw(er);
        } finally {
            currentModule && currentModule.deactivate();
        }
    },
    browse: function(moduleName, klassName, methodName) {
        var targetModule = module(moduleName),
            moduleURL = new URL(targetModule.uri()),
            dir = moduleURL.getDirectory(),
            fileName  = moduleURL.filename();

        var srcCtrl = lively.ide.startSourceControl();
        srcCtrl.addModule(targetModule.relativePath());

        this.setTargetURL(dir);
        fileName && this.inPaneSelectNodeNamed('Pane1', fileName);
        klassName && this.inPaneSelectNodeNamed('Pane2', klassName);
        methodName && this.inPaneSelectNodeNamed('Pane4', methodName);
    },
});

Object.extend(lively.ide.SystemBrowser, {
    // lively.ide.SystemBrowser.browse('lively.Examples')
    browse: function(moduleName, klassName, methodName) {
        var browser = new lively.ide.SystemBrowser();
        browser.openIn(lively.morphic.World.current());
        browser.browse(moduleName, klassName, methodName);
        return browser;
    },
});

Object.extend(lively.ide, {

    browse: function(/*args*/) {
        // args can be:
        // 1. objectName, methodName, moduleNameOrSpec, optional browser instance
        //   Browse a method in a object (class, layer, etc)
        //   See MethodFinder for original implementation
        //   Example:
        //   objectName = "lively.morphic.Morph"
        //   methodName = "onMouseDown"
        //   moduleNameOrSpec = "lively.morphic.Events"
        //     || {name: "lively.ast.LivelyJSParser", type: 'ometa'};
        // 2. URL (URL object or string), optional browser instance
        // 3. path (String) relative to URL.root, optional browser instance

        var args = Array.from(arguments);
        if (args.length === 0) { this.openSystemCodeBrowser(); return; }
        if (args.length <= 2) { // url or path or options object
            var url = args[0].toString().startsWith('http:') ?
                new URL(args[0]) : URL.root.withFilename(args[0]);
            this.browseURL(url, args[1]/*optional browser*/);
        } else {
            var objectName = args[0],
                methodName = args[1],
                moduleNameOrSpec = args[2],
                browser = args[3];

            var promise = {}, moduleName, moduleType;
            if (Object.isString(moduleNameOrSpec)) {
                moduleName = moduleNameOrSpec;
            } else if (moduleNameOrSpec.name) {
                moduleName = moduleNameOrSpec.name;
                moduleType = moduleNameOrSpec.type || moduleType;
            }

            if (objectName) {
                objectName = objectName.replace(/^Global\./,"");
            }

            if (moduleName === 'Global') {
                alert("could not browse " + methodName + " in " + objectName + "\nb/c its sourceModule is \"Global\"");
                rootNode.browseIt({browser: browser});
                return false;
            }

            var relative = module(moduleName).relativePath(moduleType),
                moduleNode = lively.ide.startSourceControl().addModule(relative),
                rootNode = moduleNode.ast(),
                fileFragments = rootNode.subElements(10).select(function(ea) {
                    var path = ea.getOwnerNamePath();
                    return path.include(objectName) && (!methodName || path.include(methodName));
                });

            if (fileFragments.length > 0) {
                return fileFragments[0].browseIt({browser: browser});
            } else {
                alert("could not browse " + methodName + " in " + objectName);
                rootNode.browseIt({browser: browser});
                return false;
            }

            return promise;
        }
    },

    browseURL: function(url, browser) {
        var path = lively.ide.sourceDB().mapURLsToRelativeModulePaths([url]).first();
        if (url.isLeaf() && !lively.ide.sourceDB().addModule(path).exists())
            return lively.ide.openFile(url);

        browser = browser || this.openSystemCodeBrowser();
        if (url.isLeaf()) {
            var dir = url.getDirectory(),
                fileName = url.filename();
            browser.setTargetURL(dir);
            browser.selectNodeMatching(function(node) {
                return node && node.url && node.url().filename() == fileName; });
        } else browser.setTargetURL(url);
        browser.panel.getWindow().comeForward();
        return browser;
    },

    openSystemCodeBrowser: function() {
        var browser = new lively.ide.SystemBrowser();
        browser.openIn(lively.morphic.World.current());
        return browser;
    }

});

}) // end of module
<|endoftext|>


<commit_before>var parser = require('./parser'),
	fs = require('fs'),
	blacklistedNames = {
		_name: 1, _value: 1, _remove: 1, _add: 1,
		_getString: 1, _root: 1, toString: 1, _comments: 1,
		_isVerbatim: 1, _addVerbatimBlock: 1
	};

function createConfItem(file, context, node) {
	var name = node.name,
		value = node.value,
		children = node.children,
		comments = node.comments || [];

	var newContext = {
		_remove: function(name, index) {
			index = Math.max(index || 0, 0);
			if (!this[name]) {
				return this;
			}

			var node = this[name];
			if (Array.isArray(this[name])) {
				if (this[name][index]) {
					node = this[name][index];
					this[name].splice(index, 1);
					if (this[name].length === 1) {
						this[name] = this[name][0];
					}
					file.emit('removed', node);
				}
			} else {
				node = this[name];
				delete this[name];
				file.emit('removed', node);
			}

			return this;
		},

		_add: function(name, value, children, comments, options) {
			if (blacklistedNames[name]) {
				throw new Error('The name "' + name + '" is reserved');
			}

			options = options || {};

			var node = createConfItem(file, newContext, {
				name: name,
				value: value,
				children: children,
				comments: comments,
				isVerbatim: !!options.isVerbatim
			});
			file.emit('added', node);
			return this;
		},

		_addVerbatimBlock: function(name, value, comments) {
			return this._add(name, value, null, comments, {
				isVerbatim: true
			});
		},

		_getString: function(depth) {
			depth = depth || +!this._root;
			var prefix = new Array(depth).join(file.tab),
				buffer = '',
				i;

			if (this._comments.length) {
				for (i = 0; i < this._comments.length; i++) {
					buffer += '#' + this._comments[i] + '\n';
				}
			}

			buffer += prefix + (!this._root ? this._name : '');

			if (this._isVerbatim) {
				buffer += ' {' + (this._value || '') + '}';
			} else if (this._value) {
				buffer += ' ' + this._value;
			}

			var properties = Object.keys(this)
				.filter(function(key) {
					return typeof(newContext[key]) !== 'function';
				})
				.map(function(key) {
					return newContext[key];
				});

			if (properties.length) {
				if (!this._root) {
					buffer += ' {\n';
				}
				for (i = 0; i < properties.length; i++) {
					var prop = properties[i];
					if (Array.isArray(prop)) {
						for (var j = 0; j < prop.length; j++) {
							buffer += prop[j]._getString(depth + 1);
						}
					} else {
						buffer += prop._getString(depth + 1);
					}
				}
				if (!this._root) {
					buffer += prefix + '}\n';
				}
			} else if (!this._root) {
				if (!this._isVerbatim) {
					buffer += ';';
				}
				buffer += '\n';
			}

			return buffer;
		},

		toString: function() {
			return this._getString(0);
		}
	};

	Object.defineProperty(newContext, '_value', {
		enumerable: false,
		get: function() {
			return value;
		},
		set: function(newValue) {
			newValue = newValue.toString();
			if (value === newValue) {
				return;
			}

			var oldValue = value;
			value = newValue;
			file.emit('changed', newContext, oldValue);
		}
	});

	Object.defineProperty(newContext, '_isVerbatim', {
		enumerable: false,
		value: node.isVerbatim,
		writable: false
	});

	Object.defineProperty(newContext, '_name', {
		enumerable: false,
		value: name,
		writable: false
	});

	Object.defineProperty(newContext, '_comments', {
		enumerable: false,
		value: comments,
		writable: false
	});

	if (context[name]) {
		//already exists, create an array or append it to the new one
		if (!Array.isArray(context[name])) {
			context[name] = [ context[name] ];
		}

		context[name].push(newContext);
	} else {
		context[name] = newContext;
	}

	if (children) {
		for (var i = 0; i < children.length; i++) {
			createConfItem(file, newContext, children[i]);
		}
	}

	return newContext;
}

function NginxConfFile(tree, options) {
	options = options || {};
	this.files = [];
	this.tab = options.tab || '    ';
	this._name = 'NginxConfFile';
	this.liveListener = (function(file) {
		return function() {
			file.flush();
		};
	}(this));
	this.writeTimeout = null;

	createConfItem(this, this, { name: 'nginx' });
	Object.defineProperty(this.nginx, '_root', {
		writable: false,
		value: true,
		enumerable: false
	});
	for (var i = 0; i < tree.children.length; i++) {
		var node = tree.children[i];
		createConfItem(this, this.nginx, node);
	}
}

NginxConfFile.prototype.__proto__ = require('events').EventEmitter.prototype;

NginxConfFile.prototype.live = function(file) {
	if (this.files.indexOf(file) === -1) {
		this.files.push(file);
		if (this.files.length === 1) {
			this.on('added', this.liveListener);
			this.on('removed', this.liveListener);
			this.on('changed', this.liveListener);
		}
	}

	return this;
};

NginxConfFile.prototype.die = function(file) {
	var index = this.files.indexOf(file);
	if (index !== -1) {
		this.files.splice(index, 1);
		if (this.files.length == 0) {
			this.removeListener('added removed changed', this.liveListener);
		}
	}

	return this;
};

/**
 * To handle potentially concurrent writes, use flush() instead.
 */
NginxConfFile.prototype.write = function(callback) {
	if (!this.files.length) {
		callback && callback(null, false);
		return;
	}

	var contents = this.toString(),
		len = this.files.length,
		errors = [],
		completed = 0;

	for (var i = 0; i < len; i++) {
		fs.writeFile(this.files[i], contents, 'utf8', function(err) {
			err && errors.push(err);
			completed++;
			if (completed === len) {
				callback && callback(errors.length ? errors : null, true);
			}
		});
	}
};

NginxConfFile.prototype.flush = function(callback) {
	if (this.writeTimeout) {
		clearTimeout(this.writeTimeout);
	}

	//the call to write() gets shoved into the event loop so that
	//you can modify the tree more than once without hoping that
	//no race conditions occur

	//e.g. conf._remove('foo'); conf._remove('bar'); will only issue
	//one call to write(), eliminating any possible race conditions
	var self = this;
	this.writeTimeout = setTimeout(function() {
		self.write(function(err, wrote) {
			if (!err && wrote) {
				self.emit('flushed');
			}

			callback && callback(err);
		});
	}, 1);
};

NginxConfFile.prototype.toString = function() {
	return this.nginx.toString();
};

NginxConfFile.create = function(file, options, callback) {
	if (typeof(options) === 'function') {
		callback = options;
		options = null;
	}

	parser.parseFile(file, 'utf8', function(err, tree) {
		if (err) {
			callback && callback(err);
			return;
		}

		callback && callback(null, new NginxConfFile(tree, options).live(file));
	});
};

NginxConfFile.createFromSource = function(source, options, callback) {
	if (typeof(options) === 'function') {
		callback = options;
		options = null;
	}

	parser.parse(source, function(err, tree) {
		if (err) {
			callback && callback(err);
			return;
		}

		callback && callback(null, new NginxConfFile(tree, options));
	});
};

exports.NginxConfFile = NginxConfFile;
<commit_msg>don't use ==<commit_after>var parser = require('./parser'),
	fs = require('fs'),
	blacklistedNames = {
		_name: 1, _value: 1, _remove: 1, _add: 1,
		_getString: 1, _root: 1, toString: 1, _comments: 1,
		_isVerbatim: 1, _addVerbatimBlock: 1
	};

function createConfItem(file, context, node) {
	var name = node.name,
		value = node.value,
		children = node.children,
		comments = node.comments || [];

	var newContext = {
		_remove: function(name, index) {
			index = Math.max(index || 0, 0);
			if (!this[name]) {
				return this;
			}

			var node = this[name];
			if (Array.isArray(this[name])) {
				if (this[name][index]) {
					node = this[name][index];
					this[name].splice(index, 1);
					if (this[name].length === 1) {
						this[name] = this[name][0];
					}
					file.emit('removed', node);
				}
			} else {
				node = this[name];
				delete this[name];
				file.emit('removed', node);
			}

			return this;
		},

		_add: function(name, value, children, comments, options) {
			if (blacklistedNames[name]) {
				throw new Error('The name "' + name + '" is reserved');
			}

			options = options || {};

			var node = createConfItem(file, newContext, {
				name: name,
				value: value,
				children: children,
				comments: comments,
				isVerbatim: !!options.isVerbatim
			});
			file.emit('added', node);
			return this;
		},

		_addVerbatimBlock: function(name, value, comments) {
			return this._add(name, value, null, comments, {
				isVerbatim: true
			});
		},

		_getString: function(depth) {
			depth = depth || +!this._root;
			var prefix = new Array(depth).join(file.tab),
				buffer = '',
				i;

			if (this._comments.length) {
				for (i = 0; i < this._comments.length; i++) {
					buffer += '#' + this._comments[i] + '\n';
				}
			}

			buffer += prefix + (!this._root ? this._name : '');

			if (this._isVerbatim) {
				buffer += ' {' + (this._value || '') + '}';
			} else if (this._value) {
				buffer += ' ' + this._value;
			}

			var properties = Object.keys(this)
				.filter(function(key) {
					return typeof(newContext[key]) !== 'function';
				})
				.map(function(key) {
					return newContext[key];
				});

			if (properties.length) {
				if (!this._root) {
					buffer += ' {\n';
				}
				for (i = 0; i < properties.length; i++) {
					var prop = properties[i];
					if (Array.isArray(prop)) {
						for (var j = 0; j < prop.length; j++) {
							buffer += prop[j]._getString(depth + 1);
						}
					} else {
						buffer += prop._getString(depth + 1);
					}
				}
				if (!this._root) {
					buffer += prefix + '}\n';
				}
			} else if (!this._root) {
				if (!this._isVerbatim) {
					buffer += ';';
				}
				buffer += '\n';
			}

			return buffer;
		},

		toString: function() {
			return this._getString(0);
		}
	};

	Object.defineProperty(newContext, '_value', {
		enumerable: false,
		get: function() {
			return value;
		},
		set: function(newValue) {
			newValue = newValue.toString();
			if (value === newValue) {
				return;
			}

			var oldValue = value;
			value = newValue;
			file.emit('changed', newContext, oldValue);
		}
	});

	Object.defineProperty(newContext, '_isVerbatim', {
		enumerable: false,
		value: node.isVerbatim,
		writable: false
	});

	Object.defineProperty(newContext, '_name', {
		enumerable: false,
		value: name,
		writable: false
	});

	Object.defineProperty(newContext, '_comments', {
		enumerable: false,
		value: comments,
		writable: false
	});

	if (context[name]) {
		//already exists, create an array or append it to the new one
		if (!Array.isArray(context[name])) {
			context[name] = [ context[name] ];
		}

		context[name].push(newContext);
	} else {
		context[name] = newContext;
	}

	if (children) {
		for (var i = 0; i < children.length; i++) {
			createConfItem(file, newContext, children[i]);
		}
	}

	return newContext;
}

function NginxConfFile(tree, options) {
	options = options || {};
	this.files = [];
	this.tab = options.tab || '    ';
	this._name = 'NginxConfFile';
	this.liveListener = (function(file) {
		return function() {
			file.flush();
		};
	}(this));
	this.writeTimeout = null;

	createConfItem(this, this, { name: 'nginx' });
	Object.defineProperty(this.nginx, '_root', {
		writable: false,
		value: true,
		enumerable: false
	});
	for (var i = 0; i < tree.children.length; i++) {
		var node = tree.children[i];
		createConfItem(this, this.nginx, node);
	}
}

NginxConfFile.prototype.__proto__ = require('events').EventEmitter.prototype;

NginxConfFile.prototype.live = function(file) {
	if (this.files.indexOf(file) === -1) {
		this.files.push(file);
		if (this.files.length === 1) {
			this.on('added', this.liveListener);
			this.on('removed', this.liveListener);
			this.on('changed', this.liveListener);
		}
	}

	return this;
};

NginxConfFile.prototype.die = function(file) {
	var index = this.files.indexOf(file);
	if (index !== -1) {
		this.files.splice(index, 1);
		if (!this.files.length) {
			this.removeListener('added removed changed', this.liveListener);
		}
	}

	return this;
};

/**
 * To handle potentially concurrent writes, use flush() instead.
 */
NginxConfFile.prototype.write = function(callback) {
	if (!this.files.length) {
		callback && callback(null, false);
		return;
	}

	var contents = this.toString(),
		len = this.files.length,
		errors = [],
		completed = 0;

	for (var i = 0; i < len; i++) {
		fs.writeFile(this.files[i], contents, 'utf8', function(err) {
			err && errors.push(err);
			completed++;
			if (completed === len) {
				callback && callback(errors.length ? errors : null, true);
			}
		});
	}
};

NginxConfFile.prototype.flush = function(callback) {
	if (this.writeTimeout) {
		clearTimeout(this.writeTimeout);
	}

	//the call to write() gets shoved into the event loop so that
	//you can modify the tree more than once without hoping that
	//no race conditions occur

	//e.g. conf._remove('foo'); conf._remove('bar'); will only issue
	//one call to write(), eliminating any possible race conditions
	var self = this;
	this.writeTimeout = setTimeout(function() {
		self.write(function(err, wrote) {
			if (!err && wrote) {
				self.emit('flushed');
			}

			callback && callback(err);
		});
	}, 1);
};

NginxConfFile.prototype.toString = function() {
	return this.nginx.toString();
};

NginxConfFile.create = function(file, options, callback) {
	if (typeof(options) === 'function') {
		callback = options;
		options = null;
	}

	parser.parseFile(file, 'utf8', function(err, tree) {
		if (err) {
			callback && callback(err);
			return;
		}

		callback && callback(null, new NginxConfFile(tree, options).live(file));
	});
};

NginxConfFile.createFromSource = function(source, options, callback) {
	if (typeof(options) === 'function') {
		callback = options;
		options = null;
	}

	parser.parse(source, function(err, tree) {
		if (err) {
			callback && callback(err);
			return;
		}

		callback && callback(null, new NginxConfFile(tree, options));
	});
};

exports.NginxConfFile = NginxConfFile;
<|endoftext|>


<commit_before>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        var directives = {};
        this.$el = document.getElementById(_el);
        this.components = opts.components;
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        // Change state when $data is changed
        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        // Utility: Raw Attributes -> Key Value Pairs
        var extractAttrs = function(node) {
          var attrs = {};
          if(!node.attributes) return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        // Utility: Compile a template and return html
        var compileTemplate = function(template, data) {
          var code = template,
              re = /{{([.]?\w+)}}/gi;
          code.replace(re, function(match, p) {
            code = code.replace(match, "` + data." + p + " + `");
          });
          var compile = new Function("data", "var out = `" + code + "`; return out");
          return compile(data);
        }

        // Utility: Create Elements Recursively For all Children
        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, extractAttrs(child), child));
          }
          return recursiveChildrenArr;
        }

        // Utility: Create Virtual DOM Instance
        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, extractAttrs(node), node);
          this.dom = vdom;
        }

        // Build the DOM with $data
        this.build = function(children) {
          for(var i = 0; i < children.length; i++) {
            var el = children[i];

            if(el.type === "#text") {
              el.node.textContent = compileTemplate(el.val, this.$data);
            } else if(el.props) {
              for(var prop in el.props) {
                var propVal = el.props[prop];
                var compiledProperty = compileTemplate(propVal, this.$data);
                var directive = directives[prop];
                if(directive) {
                  directive(el.node, compiledProperty);
                }

                el.node.setAttribute(prop, compiledProperty);
              }
            }

            this.build(el.children);
          }
        }

        // Create Virtual DOM Object from Params
        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        // Set any value in $data
        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        // Get any value in $data
        this.get = function(key) {
          return this.$data[key];
        }

        // Make AJAX GET/POST requests
        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        // Call a method defined in _methods
        this.method = function(method) {
          _methods[method]();
        }

        // Directive Initialization
        this.directive = function(name, action) {
          directives["m-" + name] = action;
          this.build(this.dom.children);
        }

        // Default Directives
        var dirSelf = this;
        directives["m-if"] = function(el, val) {
          var evaluated = new Function("return " + val);
          if(!evaluated) {
            el.textContent = "";
          }
        }

        // Initialize
        this.createVirtualDOM(this.$el);
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<commit_msg>check if there are components<commit_after>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        var directives = {};
        this.$el = document.getElementById(_el);
        this.components = opts.components;
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        // Change state when $data is changed
        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        // Utility: Raw Attributes -> Key Value Pairs
        var extractAttrs = function(node) {
          var attrs = {};
          if(!node.attributes) return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        // Utility: Compile a template and return html
        var compileTemplate = function(template, data) {
          var code = template,
              re = /{{([.]?\w+)}}/gi;
          code.replace(re, function(match, p) {
            code = code.replace(match, "` + data." + p + " + `");
          });
          var compile = new Function("data", "var out = `" + code + "`; return out");
          return compile(data);
        }

        // Utility: Create Elements Recursively For all Children
        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, extractAttrs(child), child));
          }
          return recursiveChildrenArr;
        }

        // Utility: Create Virtual DOM Instance
        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, extractAttrs(node), node);
          this.dom = vdom;
        }

        // Build the DOM with $data
        this.build = function(children) {
          for(var i = 0; i < children.length; i++) {
            var el = children[i];

            if(el.type === "#text") {
              el.node.textContent = compileTemplate(el.val, this.$data);
            } else if(el.props) {
              for(var prop in el.props) {
                var propVal = el.props[prop];
                var compiledProperty = compileTemplate(propVal, this.$data);
                var directive = directives[prop];
                if(directive) {
                  directive(el.node, compiledProperty);
                }

                el.node.setAttribute(prop, compiledProperty);
              }
            }

            if(this.components[el.type.toLowerCase()]) {

            }

            this.build(el.children);
          }
        }

        // Create Virtual DOM Object from Params
        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        // Set any value in $data
        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        // Get any value in $data
        this.get = function(key) {
          return this.$data[key];
        }

        // Make AJAX GET/POST requests
        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        // Call a method defined in _methods
        this.method = function(method) {
          _methods[method]();
        }

        // Directive Initialization
        this.directive = function(name, action) {
          directives["m-" + name] = action;
          this.build(this.dom.children);
        }

        // Default Directives
        var dirSelf = this;
        directives["m-if"] = function(el, val) {
          var evaluated = new Function("return " + val);
          if(!evaluated) {
            el.textContent = "";
          }
        }

        // Initialize
        this.createVirtualDOM(this.$el);
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<|endoftext|>


<commit_before>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        this.$el = document.getElementById(_el);
        this.html = this.$el.innerHTML;
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        this.build = function(children) {
          var tempData = this.$data;
          for(var i = 0; i < children.length; i++) {
            var el = children[i];
            if(el.type === "#text") {
              el.val.replace(/{{(\w+)}}/gi, function(match, p1) {
                var dataToAdd = tempData[p1];
                el.node.textContent = el.val.replace(new RegExp(match, "gi"), dataToAdd);
              });
            } else {
               this.build(el.children);
            }
          }
        }

        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, child));
          }
          return recursiveChildrenArr;
        }

        this.createElement = function(type, children, val, node) {
          return {type: type, children: children, val: val, node: node};
        }

        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, node);
          this.dom = vdom;
        }

        this.seed = function() {
          this.createVirtualDOM(this.$el);
        }

        this.extractAttrs = function(node) {
          var attrs = {};
          if(node.nodeName === "#text") return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        this.get = function(key) {
          return this.$data[key];
        }

        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        this.method = function(method) {
          _methods[method]();
        }

        this.seed();
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<commit_msg>props<commit_after>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        this.$el = document.getElementById(_el);
        this.html = this.$el.innerHTML;
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        this.build = function(children) {
          var tempData = this.$data;
          for(var i = 0; i < children.length; i++) {
            var el = children[i];
            if(el.type === "#text") {
              el.val.replace(/{{(\w+)}}/gi, function(match, p1) {
                var dataToAdd = tempData[p1];
                el.node.textContent = el.val.replace(new RegExp(match, "gi"), dataToAdd);
              });
            } else {
               this.build(el.children);
            }
          }
        }

        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, child));
          }
          return recursiveChildrenArr;
        }

        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, node);
          this.dom = vdom;
        }

        this.seed = function() {
          this.createVirtualDOM(this.$el);
        }

        this.extractAttrs = function(node) {
          var attrs = {};
          if(node.nodeName === "#text") return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        this.get = function(key) {
          return this.$data[key];
        }

        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        this.method = function(method) {
          _methods[method]();
        }

        this.seed();
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<|endoftext|>


<commit_before>/* globals
 DEBUG: true,
 CONTINUOUS_INTEGRATION: true
 */

DEBUG = !!process.env.VELOCITY_DEBUG;
CONTINUOUS_INTEGRATION = process.env.VELOCITY_CI;

/**
 * @module Velocity
 * @class Velocity
 */
(function () {
  'use strict';

//////////////////////////////////////////////////////////////////////
// Init
//

  DEBUG && console.log('[velocity] adding velocity core');
  CONTINUOUS_INTEGRATION && console.log('[velocity] is in continuous integration mode');

  var _ = Npm.require('lodash');
  var files = VelocityMeteorInternals.files;
  VelocityInternals.frameworkConfigs = {};
  var _watcher;
  var _velocityStarted = false;
  var _velocityStartupFunctions = [];
  var FIXTURE_REG_EXP = new RegExp('-fixture.(js|coffee)$');

  _removeTerminatedMirrors();

  _setReusableMirrors();

  if (process.env.NODE_ENV === 'development' &&
    process.env.VELOCITY !== '0' &&
    !process.env.IS_MIRROR
  ) {
    Meteor.startup(function initializeVelocity () {
      DEBUG && console.log('[velocity] Server startup');
      DEBUG && console.log('[velocity] app dir', Velocity.getAppPath());
      DEBUG && console.log('[velocity] config =', JSON.stringify(VelocityInternals.frameworkConfigs, null, 2));

      //kick-off everything
      _resetAll();

      _initFileWatcher(VelocityInternals.frameworkConfigs, _triggerVelocityStartupFunctions);

      _launchContinuousIntegration(VelocityInternals.frameworkConfigs);

    });
  }

//////////////////////////////////////////////////////////////////////
// Public Methods
//

  _.extend(Velocity, {

    /**
     * Run code when Velocity is started.
     *
     * Velocity is considered started when the file watcher has
     * completed the scan of the file system.
     *
     * @method startup
     * @return {function} A function to run on startup
     */
    startup: function (func) {
      if (_velocityStarted) {
        DEBUG && console.log('[velocity] Velocity already started. Immediately calling func');
        func();
      } else {
        DEBUG && console.log('[velocity] Velocity not started. Queueing func');
        _velocityStartupFunctions.push(func);
      }
    },

    /**
     * Get the application root path.
     *
     * @method getAppPath
     * @return {String} app root path
     */
    getAppPath: function () {
      var appPath = files.findAppDir();
      if (appPath) {
        appPath = files.pathResolve(appPath);
      }

      return files.convertToOSPath(appPath);
    },

    /**
     * Get path to application's or application package's 'tests' directory
     *
     * @method getTestsPath
     * @param {String} packageName optional package name
     * @return {String} application's tests directory
     */
    getTestsPath: function (packageName) {
      return files.convertToOSPath(
        files.pathJoin(packageName ? Velocity.getPackagePath(packageName) : Velocity.getAppPath(), 'tests')
      );
    },

    /**
     * Get path to application's 'packages' directory
     *
     * @method getPackagesPath
     * @return {String} application's packages directory
     */
    getPackagesPath: function () {
      return files.convertToOSPath(files.pathJoin(Velocity.getAppPath(), 'packages'));
    },

    /**
     * Get path to application's package directory
     *
     * @method getPackagesPath
     * @param {String} packageName package name
     * @return {String} application's packages directory
     */
    getPackagePath: function (packageName) {
      return files.convertToOSPath(files.pathJoin(Velocity.getPackagesPath(), packageName));
    },

    /**
     * A collection of callbacks to be executed after all tests have completed
     * and the aggregate test results have been reported.
     *
     * See {{#crossLink 'Velocity/addPostProcessor:method'}}{{/crossLink}}
     *
     * @property postProcessors
     * @type Array
     * @default []
     */
    postProcessors: [],

    /**
     * Add a callback which will execute after all tests have completed
     * and after the aggregate test results have been reported.
     *
     * @method addPostProcessor
     * @param {Function} processor
     */
    addPostProcessor: function (processor) {
      Velocity.postProcessors.push(processor);
    },

    /**
     * Get a message that displays where bugs in Velocity core itself should
     * be reported.
     *
     * @method getReportGithubIssueMessage
     * @return {String} message with bug repo url
     */
    getReportGithubIssueMessage: function () {
      return 'Please report the issue here: ' +
        'https://github.com/meteor-velocity/velocity/issues';
    },

    /**
     * Registers a testing framework plugin.
     *
     * @method registerTestingFramework
     * @param {String} name The name of the testing framework.
     * @param {Object} [options] Options for the testing framework.
     *   @param {String} [options.regex] The regular expression for test files
     *                    that should be assigned to the testing framework.
     *                    The path relative to the tests folder is matched
     *                    against it. Default: 'name/.+\.js$' (name is
     *                    the testing framework name).
     *   @param {String} [options.disableAutoReset]   Velocity's reset cycle
     *                    will skip reports and logs for this framework.
     *                    It is up to the framework to clean up its ****!
     *   @param {Function} [options.sampleTestGenerator] sampleTestGenerator
     *                    returns an array of fileObjects with the following
     *                    fields:
     *                      path - String - relative path to place test files
     *                                      (from PROJECT/tests)
     *                      contents - String - contents to put in the test file
     *                                          at the corresponding path
     */
    registerTestingFramework: function (name, options) {
      DEBUG && console.log('[velocity] Register framework ' + name + ' with regex ' + options.regex);
      VelocityInternals.frameworkConfigs[name] = VelocityInternals.parseTestingFrameworkOptions(name, options);
      // make sure the appropriate aggregate records are added
      Velocity.Collections.AggregateReports.insert({
        name: name,
        result: 'pending'
      });
    },

    /**
     * Unregister a testing framework.  Mostly used for internal testing
     * of core Velocity functions.
     *
     * @method unregisterTestingFramework
     * @param {String} name Name of framework to unregister
     */
    unregisterTestingFramework: function (name) {
      Velocity.Collections.TestReports.remove({framework: name});
      Velocity.Collections.Logs.remove({framework: name});
      Velocity.Collections.AggregateReports.remove({name: name});
      Velocity.Collections.TestFiles.remove({targetFramework: name});

      delete VelocityInternals.frameworkConfigs[name];
    }
  });

//////////////////////////////////////////////////////////////////////
// Private functions
//

  function _triggerVelocityStartupFunctions () {
    _velocityStarted = true;
    DEBUG && console.log('[velocity] Triggering queued startup functions');

    while (_velocityStartupFunctions.length) {
      var func = _velocityStartupFunctions.pop();
      func();
    }
  }

   VelocityInternals.parseTestingFrameworkOptions = function (name, options) {
    options = options || {};
    _.defaults(options, {
      name: name,
      regex: name + '/.+\\.js$'
    });

    options._regexp = new RegExp(options.regex);

    return options;
  };

  // Runs each test framework once when in continous integration mode.
  function _launchContinuousIntegration () {

    if (CONTINUOUS_INTEGRATION) {
      _.forEach(_getTestFrameworkNames(), function (testFramework) {
        Meteor.call('velocity/logs/reset', {framework: testFramework}, function () {

          Meteor.call(testFramework + '/reset', function () {});
          Meteor.call(testFramework + '/run', function () {});
        });
      });
    }
  }

  /**
   * Initialize the directory/file watcher.
   *
   * @method _initFileWatcher
   * @param {Object} config See {{#crossLink 'Velocity/registerTestingFramework:method'}}{{/crossLink}}
   * @param {function} callback  Called after the watcher completes its first scan and is ready
   * @private
   */
  function _initFileWatcher (config, callback) {
    var paths,
        packagesPath;

    Velocity.Collections.TestFiles.remove({});
    Velocity.Collections.FixtureFiles.remove({});

    paths = [Velocity.getTestsPath()];
    packagesPath = Velocity.getPackagesPath();

    if (VelocityInternals.isDirectory(packagesPath)) {
      var packageNames = files.readdir(packagesPath),
          packageTestsPaths = _.chain(packageNames)
            .filter(_isPackageWithTests)
            .map(Velocity.getTestsPath)
            .value();
      paths.push.apply(paths, packageTestsPaths);
    }

    paths = _.map(paths, files.convertToOSPath);

    DEBUG && console.log('[velocity] Add paths to watcher', paths);

    _watcher = chokidar.watch(paths, {
      ignored: /[\/\\](\.|node_modules)/,
      persistent: true
    });
    _watcher.on('add', Meteor.bindEnvironment(function (filePath) {
      var relativePath,
          packageRelativePath,
          targetFramework,
          data;

      filePath = files.convertToStandardPath(files.pathNormalize(filePath));
      relativePath = _getRelativePath(filePath);

      // if this is a fixture file, put it in the fixtures collection
      if (FIXTURE_REG_EXP.test(relativePath)) {
        DEBUG && console.log('[velocity] Found fixture file', relativePath);
        Velocity.Collections.FixtureFiles.insert({
          _id: filePath,
          absolutePath: filePath,
          relativePath: relativePath,
          lastModified: Date.now()
        });
        // bail early
        return;
      }

      DEBUG && console.log('[velocity] Search framework for path', relativePath);

      packageRelativePath = (relativePath.indexOf('packages') === 0) ?
        relativePath.split('/').slice(2).join('/') :
        relativePath;

      // test against each test framework's regexp matcher, use first one that matches
      targetFramework = _.find(config, function (framework) {
        return framework._regexp.test(packageRelativePath);
      });

      if (targetFramework) {
        DEBUG && console.log('[velocity] Target framework for', relativePath, 'is', targetFramework.name);

        data = {
          _id: filePath,
          name: files.pathBasename(filePath),
          absolutePath: filePath,
          relativePath: relativePath,
          targetFramework: targetFramework.name,
          lastModified: Date.now()
        };

        Velocity.Collections.TestFiles.insert(data);
      } else {
        DEBUG && console.log('[velocity] No framework registered for', relativePath);
      }
    }));  // end watcher.on 'add'

    _watcher.on('change', Meteor.bindEnvironment(function (filePath) {
      DEBUG && console.log('[velocity] File changed:', _getRelativePath(filePath));

      // Since we key on filePath and we only add files we're interested in,
      // we don't have to worry about inadvertently updating records for files
      // we don't care about.
      filePath = files.convertToStandardPath(files.pathNormalize(filePath));
      Velocity.Collections.TestFiles.update(filePath, {$set: {lastModified: Date.now()}});
    }));

    _watcher.on('unlink', Meteor.bindEnvironment(function (filePath) {
      filePath = files.convertToStandardPath(files.pathNormalize(filePath));
      DEBUG && console.log('[velocity] File removed:',
        _getRelativePath(filePath));

      Velocity.Collections.TestFiles.remove(filePath);
    }));

    _watcher.on('ready', Meteor.bindEnvironment(function () {
      DEBUG && console.log('[velocity] File scan complete, now watching',
        Velocity.getTestsPath().substring(Velocity.getAppPath().length));

      callback && callback();
    }));

  }  // end _initFileWatcher

  function _isPackageWithTests(packageName) {
    return packageName !== 'tests-proxy' &&
      VelocityInternals.isDirectory(Velocity.getTestsPath(packageName));
  }

  /**
   * Clear test reports, aggregate reports, and logs for a specific framework.
   *
   * @method VelocityInternals.reset
   * @param {String} name Framework to reset
   */
  VelocityInternals.reset = function (name) {
    DEBUG && console.log('[velocity] resetting', name);

    Velocity.Collections.Logs.remove({framework: name});
    Velocity.Collections.TestReports.remove({framework: name});
    Velocity.Collections.AggregateReports.remove({name: name});

    Velocity.Collections.AggregateReports.insert({
      name: name,
      result: 'pending'
    });
  };

  /**
   * Clear all test reports, aggregate reports, and logs.
   *
   * @method _resetAll
   * @param {Object} config See {{#crossLink 'Velocity/registerTestingFramework:method'}}{{/crossLink}}
   * @private
   */
  function _resetAll () {
    var allFrameworks,
        frameworksToIgnore;

    DEBUG && console.log('[velocity] resetting the world');

    allFrameworks = _getTestFrameworkNames();

    // ignore frameworks that have opted-out
    frameworksToIgnore = _(VelocityInternals.frameworkConfigs)
      .where({disableAutoReset: true})
      .pluck('_resetAllname')
      .value();

    DEBUG && console.log('[velocity] frameworks with disable auto reset:',
      frameworksToIgnore);

    Velocity.Collections.AggregateReports.remove({});
    Velocity.Collections.Logs.remove({framework: {$nin: frameworksToIgnore}});
    Velocity.Collections.TestReports.remove({framework: {$nin: frameworksToIgnore}});

    _.forEach(allFrameworks, function (testFramework) {
      Velocity.Collections.AggregateReports.insert({
        name: testFramework,
        result: 'pending'
      });
    });
  }

  /**
   * If any one test has failed, mark the aggregate test result as failed.
   *
   * @method VelocityInternals.updateAggregateReports
   */
  VelocityInternals.updateAggregateReports = function  () {
    var aggregateResult,
        completedFrameworksCount,
        allFrameworks = _getTestFrameworkNames();

    Velocity.Collections.AggregateReports.upsert({name: 'aggregateResult'},
      {$set: {result: 'pending'}});
    Velocity.Collections.AggregateReports.upsert({name: 'aggregateComplete'},
      {$set: {result: 'pending'}});

    // if all of our test reports have valid results
    if (!Velocity.Collections.TestReports.findOne({result: ''})) {

      // pessimistically set passed state, ensuring all other states
      // take precedence in order below
      aggregateResult =
        Velocity.Collections.TestReports.findOne({result: 'failed'}) ||
        Velocity.Collections.TestReports.findOne({result: 'undefined'}) ||
        Velocity.Collections.TestReports.findOne({result: 'skipped'}) ||
        Velocity.Collections.TestReports.findOne({result: 'pending'}) ||
        Velocity.Collections.TestReports.findOne({result: 'passed'}) ||
        {result: 'pending'};

      // update the global status
      Velocity.Collections.AggregateReports.update({name: 'aggregateResult'},
        {$set: {result: aggregateResult.result}});
    }

    // Check if all test frameworks have completed successfully
    completedFrameworksCount = Velocity.Collections.AggregateReports.find({
      name: {$in: allFrameworks},
      result: 'completed'
    }).count();

    if (allFrameworks.length === completedFrameworksCount) {
      Velocity.Collections.AggregateReports.update({name: 'aggregateComplete'},
        {$set: {'result': 'completed'}});
      _.each(Velocity.postProcessors, function (processor) {
        processor();
      });
    }
  };

  function _getRelativePath (filePath) {
    var relativePath = filePath.substring(Velocity.getAppPath().length);

    if (relativePath[0] === '/') {
      relativePath = relativePath.substring(1);
    }
    return relativePath;
  }

  function _getTestFrameworkNames () {
    return _.pluck(VelocityInternals.frameworkConfigs, 'name');
  }

  function _removeTerminatedMirrors() {
    // Remove terminated mirrors from previous runs
    // This is needed for `meteor --test` to work properly
    Velocity.Collections.Mirrors.find({}).forEach(function(mirror) {
      try {
        process.kill(mirror.pid, 0);
      } catch (error) {
        Velocity.Collections.Mirrors.remove({pid: mirror.pid});
      }
    });
  }

  function _setReusableMirrors() {
    Velocity.reusableMirrors = [];
    Velocity.Collections.Mirrors.find({}).forEach(function(mirror) {
      mirror.reused = false;
      Velocity.reusableMirrors.push(mirror);
    });
  }

})();
<commit_msg>Memoize getAppPath result for performance<commit_after>/* globals
 DEBUG: true,
 CONTINUOUS_INTEGRATION: true
 */

DEBUG = !!process.env.VELOCITY_DEBUG;
CONTINUOUS_INTEGRATION = process.env.VELOCITY_CI;

/**
 * @module Velocity
 * @class Velocity
 */
(function () {
  'use strict';

//////////////////////////////////////////////////////////////////////
// Init
//

  DEBUG && console.log('[velocity] adding velocity core');
  CONTINUOUS_INTEGRATION && console.log('[velocity] is in continuous integration mode');

  var _ = Npm.require('lodash');
  var files = VelocityMeteorInternals.files;
  VelocityInternals.frameworkConfigs = {};
  var _watcher;
  var _velocityStarted = false;
  var _velocityStartupFunctions = [];
  var FIXTURE_REG_EXP = new RegExp('-fixture.(js|coffee)$');

  _removeTerminatedMirrors();

  _setReusableMirrors();

  if (process.env.NODE_ENV === 'development' &&
    process.env.VELOCITY !== '0' &&
    !process.env.IS_MIRROR
  ) {
    Meteor.startup(function initializeVelocity () {
      DEBUG && console.log('[velocity] Server startup');
      DEBUG && console.log('[velocity] app dir', Velocity.getAppPath());
      DEBUG && console.log('[velocity] config =', JSON.stringify(VelocityInternals.frameworkConfigs, null, 2));

      //kick-off everything
      _resetAll();

      _initFileWatcher(VelocityInternals.frameworkConfigs, _triggerVelocityStartupFunctions);

      _launchContinuousIntegration(VelocityInternals.frameworkConfigs);

    });
  }

//////////////////////////////////////////////////////////////////////
// Public Methods
//

  _.extend(Velocity, {

    /**
     * Run code when Velocity is started.
     *
     * Velocity is considered started when the file watcher has
     * completed the scan of the file system.
     *
     * @method startup
     * @return {function} A function to run on startup
     */
    startup: function (func) {
      if (_velocityStarted) {
        DEBUG && console.log('[velocity] Velocity already started. Immediately calling func');
        func();
      } else {
        DEBUG && console.log('[velocity] Velocity not started. Queueing func');
        _velocityStartupFunctions.push(func);
      }
    },

    /**
     * Get the application root path.
     *
     * @method getAppPath
     * @return {String} app root path
     */
    getAppPath: _.memoize(function () {
      var appPath = files.findAppDir();
      if (appPath) {
        appPath = files.pathResolve(appPath);
      }

      return files.convertToOSPath(appPath);
    }),

    /**
     * Get path to application's or application package's 'tests' directory
     *
     * @method getTestsPath
     * @param {String} packageName optional package name
     * @return {String} application's tests directory
     */
    getTestsPath: function (packageName) {
      return files.convertToOSPath(
        files.pathJoin(packageName ? Velocity.getPackagePath(packageName) : Velocity.getAppPath(), 'tests')
      );
    },

    /**
     * Get path to application's 'packages' directory
     *
     * @method getPackagesPath
     * @return {String} application's packages directory
     */
    getPackagesPath: function () {
      return files.convertToOSPath(files.pathJoin(Velocity.getAppPath(), 'packages'));
    },

    /**
     * Get path to application's package directory
     *
     * @method getPackagesPath
     * @param {String} packageName package name
     * @return {String} application's packages directory
     */
    getPackagePath: function (packageName) {
      return files.convertToOSPath(files.pathJoin(Velocity.getPackagesPath(), packageName));
    },

    /**
     * A collection of callbacks to be executed after all tests have completed
     * and the aggregate test results have been reported.
     *
     * See {{#crossLink 'Velocity/addPostProcessor:method'}}{{/crossLink}}
     *
     * @property postProcessors
     * @type Array
     * @default []
     */
    postProcessors: [],

    /**
     * Add a callback which will execute after all tests have completed
     * and after the aggregate test results have been reported.
     *
     * @method addPostProcessor
     * @param {Function} processor
     */
    addPostProcessor: function (processor) {
      Velocity.postProcessors.push(processor);
    },

    /**
     * Get a message that displays where bugs in Velocity core itself should
     * be reported.
     *
     * @method getReportGithubIssueMessage
     * @return {String} message with bug repo url
     */
    getReportGithubIssueMessage: function () {
      return 'Please report the issue here: ' +
        'https://github.com/meteor-velocity/velocity/issues';
    },

    /**
     * Registers a testing framework plugin.
     *
     * @method registerTestingFramework
     * @param {String} name The name of the testing framework.
     * @param {Object} [options] Options for the testing framework.
     *   @param {String} [options.regex] The regular expression for test files
     *                    that should be assigned to the testing framework.
     *                    The path relative to the tests folder is matched
     *                    against it. Default: 'name/.+\.js$' (name is
     *                    the testing framework name).
     *   @param {String} [options.disableAutoReset]   Velocity's reset cycle
     *                    will skip reports and logs for this framework.
     *                    It is up to the framework to clean up its ****!
     *   @param {Function} [options.sampleTestGenerator] sampleTestGenerator
     *                    returns an array of fileObjects with the following
     *                    fields:
     *                      path - String - relative path to place test files
     *                                      (from PROJECT/tests)
     *                      contents - String - contents to put in the test file
     *                                          at the corresponding path
     */
    registerTestingFramework: function (name, options) {
      DEBUG && console.log('[velocity] Register framework ' + name + ' with regex ' + options.regex);
      VelocityInternals.frameworkConfigs[name] = VelocityInternals.parseTestingFrameworkOptions(name, options);
      // make sure the appropriate aggregate records are added
      Velocity.Collections.AggregateReports.insert({
        name: name,
        result: 'pending'
      });
    },

    /**
     * Unregister a testing framework.  Mostly used for internal testing
     * of core Velocity functions.
     *
     * @method unregisterTestingFramework
     * @param {String} name Name of framework to unregister
     */
    unregisterTestingFramework: function (name) {
      Velocity.Collections.TestReports.remove({framework: name});
      Velocity.Collections.Logs.remove({framework: name});
      Velocity.Collections.AggregateReports.remove({name: name});
      Velocity.Collections.TestFiles.remove({targetFramework: name});

      delete VelocityInternals.frameworkConfigs[name];
    }
  });

//////////////////////////////////////////////////////////////////////
// Private functions
//

  function _triggerVelocityStartupFunctions () {
    _velocityStarted = true;
    DEBUG && console.log('[velocity] Triggering queued startup functions');

    while (_velocityStartupFunctions.length) {
      var func = _velocityStartupFunctions.pop();
      func();
    }
  }

   VelocityInternals.parseTestingFrameworkOptions = function (name, options) {
    options = options || {};
    _.defaults(options, {
      name: name,
      regex: name + '/.+\\.js$'
    });

    options._regexp = new RegExp(options.regex);

    return options;
  };

  // Runs each test framework once when in continous integration mode.
  function _launchContinuousIntegration () {

    if (CONTINUOUS_INTEGRATION) {
      _.forEach(_getTestFrameworkNames(), function (testFramework) {
        Meteor.call('velocity/logs/reset', {framework: testFramework}, function () {

          Meteor.call(testFramework + '/reset', function () {});
          Meteor.call(testFramework + '/run', function () {});
        });
      });
    }
  }

  /**
   * Initialize the directory/file watcher.
   *
   * @method _initFileWatcher
   * @param {Object} config See {{#crossLink 'Velocity/registerTestingFramework:method'}}{{/crossLink}}
   * @param {function} callback  Called after the watcher completes its first scan and is ready
   * @private
   */
  function _initFileWatcher (config, callback) {
    var paths,
        packagesPath;

    Velocity.Collections.TestFiles.remove({});
    Velocity.Collections.FixtureFiles.remove({});

    paths = [Velocity.getTestsPath()];
    packagesPath = Velocity.getPackagesPath();

    if (VelocityInternals.isDirectory(packagesPath)) {
      var packageNames = files.readdir(packagesPath),
          packageTestsPaths = _.chain(packageNames)
            .filter(_isPackageWithTests)
            .map(Velocity.getTestsPath)
            .value();
      paths.push.apply(paths, packageTestsPaths);
    }

    paths = _.map(paths, files.convertToOSPath);

    DEBUG && console.log('[velocity] Add paths to watcher', paths);

    _watcher = chokidar.watch(paths, {
      ignored: /[\/\\](\.|node_modules)/,
      persistent: true
    });
    _watcher.on('add', Meteor.bindEnvironment(function (filePath) {
      var relativePath,
          packageRelativePath,
          targetFramework,
          data;

      filePath = files.convertToStandardPath(files.pathNormalize(filePath));
      relativePath = _getRelativePath(filePath);

      // if this is a fixture file, put it in the fixtures collection
      if (FIXTURE_REG_EXP.test(relativePath)) {
        DEBUG && console.log('[velocity] Found fixture file', relativePath);
        Velocity.Collections.FixtureFiles.insert({
          _id: filePath,
          absolutePath: filePath,
          relativePath: relativePath,
          lastModified: Date.now()
        });
        // bail early
        return;
      }

      DEBUG && console.log('[velocity] Search framework for path', relativePath);

      packageRelativePath = (relativePath.indexOf('packages') === 0) ?
        relativePath.split('/').slice(2).join('/') :
        relativePath;

      // test against each test framework's regexp matcher, use first one that matches
      targetFramework = _.find(config, function (framework) {
        return framework._regexp.test(packageRelativePath);
      });

      if (targetFramework) {
        DEBUG && console.log('[velocity] Target framework for', relativePath, 'is', targetFramework.name);

        data = {
          _id: filePath,
          name: files.pathBasename(filePath),
          absolutePath: filePath,
          relativePath: relativePath,
          targetFramework: targetFramework.name,
          lastModified: Date.now()
        };

        Velocity.Collections.TestFiles.insert(data);
      } else {
        DEBUG && console.log('[velocity] No framework registered for', relativePath);
      }
    }));  // end watcher.on 'add'

    _watcher.on('change', Meteor.bindEnvironment(function (filePath) {
      DEBUG && console.log('[velocity] File changed:', _getRelativePath(filePath));

      // Since we key on filePath and we only add files we're interested in,
      // we don't have to worry about inadvertently updating records for files
      // we don't care about.
      filePath = files.convertToStandardPath(files.pathNormalize(filePath));
      Velocity.Collections.TestFiles.update(filePath, {$set: {lastModified: Date.now()}});
    }));

    _watcher.on('unlink', Meteor.bindEnvironment(function (filePath) {
      filePath = files.convertToStandardPath(files.pathNormalize(filePath));
      DEBUG && console.log('[velocity] File removed:',
        _getRelativePath(filePath));

      Velocity.Collections.TestFiles.remove(filePath);
    }));

    _watcher.on('ready', Meteor.bindEnvironment(function () {
      DEBUG && console.log('[velocity] File scan complete, now watching',
        Velocity.getTestsPath().substring(Velocity.getAppPath().length));

      callback && callback();
    }));

  }  // end _initFileWatcher

  function _isPackageWithTests(packageName) {
    return packageName !== 'tests-proxy' &&
      VelocityInternals.isDirectory(Velocity.getTestsPath(packageName));
  }

  /**
   * Clear test reports, aggregate reports, and logs for a specific framework.
   *
   * @method VelocityInternals.reset
   * @param {String} name Framework to reset
   */
  VelocityInternals.reset = function (name) {
    DEBUG && console.log('[velocity] resetting', name);

    Velocity.Collections.Logs.remove({framework: name});
    Velocity.Collections.TestReports.remove({framework: name});
    Velocity.Collections.AggregateReports.remove({name: name});

    Velocity.Collections.AggregateReports.insert({
      name: name,
      result: 'pending'
    });
  };

  /**
   * Clear all test reports, aggregate reports, and logs.
   *
   * @method _resetAll
   * @param {Object} config See {{#crossLink 'Velocity/registerTestingFramework:method'}}{{/crossLink}}
   * @private
   */
  function _resetAll () {
    var allFrameworks,
        frameworksToIgnore;

    DEBUG && console.log('[velocity] resetting the world');

    allFrameworks = _getTestFrameworkNames();

    // ignore frameworks that have opted-out
    frameworksToIgnore = _(VelocityInternals.frameworkConfigs)
      .where({disableAutoReset: true})
      .pluck('_resetAllname')
      .value();

    DEBUG && console.log('[velocity] frameworks with disable auto reset:',
      frameworksToIgnore);

    Velocity.Collections.AggregateReports.remove({});
    Velocity.Collections.Logs.remove({framework: {$nin: frameworksToIgnore}});
    Velocity.Collections.TestReports.remove({framework: {$nin: frameworksToIgnore}});

    _.forEach(allFrameworks, function (testFramework) {
      Velocity.Collections.AggregateReports.insert({
        name: testFramework,
        result: 'pending'
      });
    });
  }

  /**
   * If any one test has failed, mark the aggregate test result as failed.
   *
   * @method VelocityInternals.updateAggregateReports
   */
  VelocityInternals.updateAggregateReports = function  () {
    var aggregateResult,
        completedFrameworksCount,
        allFrameworks = _getTestFrameworkNames();

    Velocity.Collections.AggregateReports.upsert({name: 'aggregateResult'},
      {$set: {result: 'pending'}});
    Velocity.Collections.AggregateReports.upsert({name: 'aggregateComplete'},
      {$set: {result: 'pending'}});

    // if all of our test reports have valid results
    if (!Velocity.Collections.TestReports.findOne({result: ''})) {

      // pessimistically set passed state, ensuring all other states
      // take precedence in order below
      aggregateResult =
        Velocity.Collections.TestReports.findOne({result: 'failed'}) ||
        Velocity.Collections.TestReports.findOne({result: 'undefined'}) ||
        Velocity.Collections.TestReports.findOne({result: 'skipped'}) ||
        Velocity.Collections.TestReports.findOne({result: 'pending'}) ||
        Velocity.Collections.TestReports.findOne({result: 'passed'}) ||
        {result: 'pending'};

      // update the global status
      Velocity.Collections.AggregateReports.update({name: 'aggregateResult'},
        {$set: {result: aggregateResult.result}});
    }

    // Check if all test frameworks have completed successfully
    completedFrameworksCount = Velocity.Collections.AggregateReports.find({
      name: {$in: allFrameworks},
      result: 'completed'
    }).count();

    if (allFrameworks.length === completedFrameworksCount) {
      Velocity.Collections.AggregateReports.update({name: 'aggregateComplete'},
        {$set: {'result': 'completed'}});
      _.each(Velocity.postProcessors, function (processor) {
        processor();
      });
    }
  };

  function _getRelativePath (filePath) {
    var relativePath = filePath.substring(Velocity.getAppPath().length);

    if (relativePath[0] === '/') {
      relativePath = relativePath.substring(1);
    }
    return relativePath;
  }

  function _getTestFrameworkNames () {
    return _.pluck(VelocityInternals.frameworkConfigs, 'name');
  }

  function _removeTerminatedMirrors() {
    // Remove terminated mirrors from previous runs
    // This is needed for `meteor --test` to work properly
    Velocity.Collections.Mirrors.find({}).forEach(function(mirror) {
      try {
        process.kill(mirror.pid, 0);
      } catch (error) {
        Velocity.Collections.Mirrors.remove({pid: mirror.pid});
      }
    });
  }

  function _setReusableMirrors() {
    Velocity.reusableMirrors = [];
    Velocity.Collections.Mirrors.find({}).forEach(function(mirror) {
      mirror.reused = false;
      Velocity.reusableMirrors.push(mirror);
    });
  }

})();
<|endoftext|>