<commit_before>function getRemovableChildren(node) {
    let nodes = node.successors().filter((i, ele) => {
        let eleNodes = ele.incomers().filter((i, ele) => ele.isNode());
        if (eleNodes.length === 1) {
            return true;
        } else {
            // Check if this node connects to target. If it does then it is connected
            // to multiple nodes at the same level as target and we cannot hide it.
            let hasTargetAsParent = false;
            eleNodes.forEach(e => {
                if (e.data('id') === node.data('id')) {
                    hasTargetAsParent = true;
                }

            });
            return !hasTargetAsParent;
        }
    });

    return nodes.union(nodes.connectedEdges());
}

class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsAPI, $stateParams, mcstate, $filter,
                $mdDialog, mcshow, workflowState, cyGraph) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsAPI = experimentsAPI;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.mcshow = mcshow;
        this.removedNodes = null;
        this.navigator = null;
        this.workflowState = workflowState;
        this.cyGraph = cyGraph;
        this.hiddenNodes = [];
        this.sidebarShowing = false;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcstate.subscribe('WORKSPACE$MAXIMIZED', this.myName, (maximized) => {
            this.sidebarShowing = !maximized;
        });

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);
        this.mcbus.subscribe('PROCESS$ADD', this.myName, (process) => {
            let node = this.processGraph.createProcessNode(process);
            this.cy.add(node);
            let edges = this.processGraph.createConnectingEdges(process, this.processes);
            if (edges.length) {
                this.cy.add(edges);
            }
            this.processes.push(process);
        });
        this.mcbus.subscribe('PROCESS$DELETE', this.myName, (process) => console.log('PROCESS$DELETE', process));
        this.mcbus.subscribe('EDGE$ADD', this.myName, (source, target) => console.log('EDGE$ADD', source, target));
        this.mcbus.subscribe('EDGE$DELETE', this.myName, (source, target) => console.log('EDGE$DELETE', source, target));
        this.mcbus.subscribe('WORKFLOW$HIDEOTHERS', this.myName, process => {
            let target = this.cy.filter(`node[id = "${process.id}"]`);
            let nodesToKeep = getRemovableChildren(target).union(target);
            let nodesToRemove = nodesToKeep.absoluteComplement();
            let hidden = this.cy.remove(nodesToRemove);
            if (this.hiddenNodes.length) {
                this.hiddenNodes = this.hiddenNodes.union(hidden);
            } else {
                this.hiddenNodes = hidden;
            }
        });

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = null;
            let matches = this.$filter('filter')(this.processes.plain(), search);
            if (!matches.length) {
                return;
            }
            let matchesById = _.indexBy(matches, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        };

        this.mcbus.subscribe('WORKFLOW$RESTOREHIDDEN', this.myName, () => {
            if (this.hiddenNodes.length) {
                this.hiddenNodes.restore();
                this.hiddenNodes = [];
            }
        });

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            if (!samples.length) {
                return;
            }

            let matchingProcesses = [];
            samples.forEach(sample => {
                let matches = this.$filter('filter')(this.processes.plain(), sample.id);
                matchingProcesses = matchingProcesses.concat(matches.map(p => ({id: p.id, seen: false})));
            });

            let matchesById = _.indexBy(matchingProcesses, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcbus.leave('WORKFLOW$RESTOREHIDDEN', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        this.mcbus.leave('PROCESS$ADD', this.myName);
        this.mcbus.leave('PROCESS$DELETE', this.myName);
        this.mcbus.leave('EDGE$ADD', this.myName);
        this.mcbus.leave('EDGE$DELETE', this.myName);
        this.mcstate.leave('WORKSPACE$MAXIMIZED', this.myName);
        if (this.navigator !== null) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = this.cyGraph.createGraph(g.elements, 'processesGraph');
        this.cyGraph.setOnClickForExperiment(this.cy, this.projectId, this.experimentId);

        this.ctxMenu = this.setupContextMenus();
        let completeFN = (source, target, addedEntities) => {
            let sourceProcess = source.data('details');
            let targetProcess = target.data('details');
            if (sourceProcess.output_samples.length === 1) {
                this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, sourceProcess.output_samples).then(
                    (process) => {
                        this.replaceProcess(process);
                        target.data('details', process);
                    }
                );
            } else {
                this.workflowService.chooseSamplesFromSource(sourceProcess).then(
                    (samples) => {
                        if (!samples.length) {
                            // No samples brought over, delete edge
                            addedEntities[0].remove();
                        } else {
                            this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, samples).then(
                                (process) => {
                                    this.replaceProcess(process);
                                    target.data('details', process);
                                }
                            );
                        }
                    }
                );
            }
        };
        this.cyGraph.addEdgeHandles(this.cy, completeFN);
    }

    replaceProcess(process) {
        let i = _.findIndex(this.processes, p => p.id === process.id);
        if (i !== -1) {
            this.processes.splice(i, 1);
            this.processes.push(process);
        }
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    title: 'Show Details',
                    selector: 'node, edge',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    title: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'add-child',
                    title: 'Add Child',
                    selector: 'node',
                    onClickFunction: (event) => this._addChild(event)
                },
                {
                    id: 'delete-process',
                    title: 'Delete Process',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._deleteProcess(event)
                },
                {
                    id: 'collapse',
                    title: 'Collapse',
                    selector: 'node',
                    onClickFunction: event => this._collapseNode(event)
                },
                {
                    id: 'expand',
                    title: 'Expand',
                    selector: 'node',
                    onClickFunction: event => this._expandNode(event)
                },
                {
                    id: 'hide',
                    title: 'Hide',
                    selector: 'node',
                    onClickFunction: event => this._hideNode(event)
                },
                {
                    id: 'hide-others',
                    title: 'Hide Others',
                    selector: 'node',
                    onClickFunction: event => this._hideOtherNodes(event)
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.experimentsAPI.getProcessForExperiment(this.projectId, this.experimentId, process.id).then(
                p => this.mcshow.processDetailsDialog(p, false).then(
                    updatedProcess => target.data('name', updatedProcess.name)
                )
            );
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, process.id);
    }

    _collapseNode(event) {
        let target = event.cyTarget;
        let nodes = getRemovableChildren(target);
        if (nodes.length) {
            let name = target.data('name');
            target.data('name', `+ ${name}`);
            target.data('collapsed', nodes);
            this.cy.remove(nodes);
        }
    }

    _expandNode(event) {
        let target = event.cyTarget;
        let collapsed = target.data('collapsed');
        if (collapsed) {
            collapsed.restore();
            let name = target.data('name').substring(1);
            target.data('name', name);
            target.data('collapsed', null);
        }
    }

    _hideNode(event) {
        let target = event.cyTarget;
        let nodes = getRemovableChildren(target);
        nodes = nodes.union(target);
        let hidden = this.cy.remove(nodes);
        if (this.hiddenNodes.length) {
            this.hiddenNodes = this.hiddenNodes.union(hidden);
        } else {
            this.hiddenNodes = hidden;
        }
    }

    _hideOtherNodes(event) {
        let target = event.cyTarget;
        let nodesToKeep = getRemovableChildren(target).union(target);
        let nodesToRemove = nodesToKeep.absoluteComplement();
        let hidden = this.cy.remove(nodesToRemove);
        if (this.hiddenNodes.length) {
            this.hiddenNodes = this.hiddenNodes.union(hidden);
        } else {
            this.hiddenNodes = hidden;
        }
    }

    _addChild(event) {
        let process = this.getProcessFromEvent(event);
        this.$mdDialog.show({
            templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-templates-dialog.html',
            controller: SelectProcessTemplateDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            multiple: true,
            locals: {
                parentProcess: process
            }
        });
    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class SelectProcessTemplateDialogController {
    /*@ngInject*/
    constructor($stateParams, $mdDialog, workflowService) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
        this.keepOpen = false;
    }

    addSelectedProcessTemplate(templateId) {
        this.workflowService.addChildProcessFromTemplate(templateId, this.projectId, this.experimentId,
            this.parentProcess, this.keepOpen)
    }

    done() {
        this.$mdDialog.hide();
    }

}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    }
});
<commit_msg>Refactor to move completeFN into a separate method<commit_after>function getRemovableChildren(node) {
    let nodes = node.successors().filter((i, ele) => {
        let eleNodes = ele.incomers().filter((i, ele) => ele.isNode());
        if (eleNodes.length === 1) {
            return true;
        } else {
            // Check if this node connects to target. If it does then it is connected
            // to multiple nodes at the same level as target and we cannot hide it.
            let hasTargetAsParent = false;
            eleNodes.forEach(e => {
                if (e.data('id') === node.data('id')) {
                    hasTargetAsParent = true;
                }

            });
            return !hasTargetAsParent;
        }
    });

    return nodes.union(nodes.connectedEdges());
}

class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsAPI, $stateParams, mcstate, $filter,
                $mdDialog, mcshow, workflowState, cyGraph) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsAPI = experimentsAPI;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.mcshow = mcshow;
        this.removedNodes = null;
        this.navigator = null;
        this.workflowState = workflowState;
        this.cyGraph = cyGraph;
        this.hiddenNodes = [];
        this.sidebarShowing = false;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcstate.subscribe('WORKSPACE$MAXIMIZED', this.myName, (maximized) => {
            this.sidebarShowing = !maximized;
        });

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);
        this.mcbus.subscribe('PROCESS$ADD', this.myName, (process) => {
            let node = this.processGraph.createProcessNode(process);
            this.cy.add(node);
            let edges = this.processGraph.createConnectingEdges(process, this.processes);
            if (edges.length) {
                this.cy.add(edges);
            }
            this.processes.push(process);
        });
        this.mcbus.subscribe('PROCESS$DELETE', this.myName, (process) => console.log('PROCESS$DELETE', process));
        this.mcbus.subscribe('EDGE$ADD', this.myName, (source, target) => console.log('EDGE$ADD', source, target));
        this.mcbus.subscribe('EDGE$DELETE', this.myName, (source, target) => console.log('EDGE$DELETE', source, target));
        this.mcbus.subscribe('WORKFLOW$HIDEOTHERS', this.myName, process => {
            let target = this.cy.filter(`node[id = "${process.id}"]`);
            let nodesToKeep = getRemovableChildren(target).union(target);
            let nodesToRemove = nodesToKeep.absoluteComplement();
            let hidden = this.cy.remove(nodesToRemove);
            if (this.hiddenNodes.length) {
                this.hiddenNodes = this.hiddenNodes.union(hidden);
            } else {
                this.hiddenNodes = hidden;
            }
        });

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = null;
            let matches = this.$filter('filter')(this.processes.plain(), search);
            if (!matches.length) {
                return;
            }
            let matchesById = _.indexBy(matches, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        };

        this.mcbus.subscribe('WORKFLOW$RESTOREHIDDEN', this.myName, () => {
            if (this.hiddenNodes.length) {
                this.hiddenNodes.restore();
                this.hiddenNodes = [];
            }
        });

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            if (!samples.length) {
                return;
            }

            let matchingProcesses = [];
            samples.forEach(sample => {
                let matches = this.$filter('filter')(this.processes.plain(), sample.id);
                matchingProcesses = matchingProcesses.concat(matches.map(p => ({id: p.id, seen: false})));
            });

            let matchesById = _.indexBy(matchingProcesses, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcbus.leave('WORKFLOW$RESTOREHIDDEN', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        this.mcbus.leave('PROCESS$ADD', this.myName);
        this.mcbus.leave('PROCESS$DELETE', this.myName);
        this.mcbus.leave('EDGE$ADD', this.myName);
        this.mcbus.leave('EDGE$DELETE', this.myName);
        this.mcstate.leave('WORKSPACE$MAXIMIZED', this.myName);
        if (this.navigator !== null) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = this.cyGraph.createGraph(g.elements, 'processesGraph');
        this.cyGraph.setOnClickForExperiment(this.cy, this.projectId, this.experimentId);

        this.ctxMenu = this.setupContextMenus();
        let cb = (source, target, addedEntities) => this._completeFN(source, target, addedEntities);
        this.cyGraph.addEdgeHandles(this.cy, cb);
    }

    _completeFN(source, target, addedEntities) {
        let sourceProcess = source.data('details');
        let targetProcess = target.data('details');
        if (sourceProcess.output_samples.length === 1) {
            this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, sourceProcess.output_samples).then(
                (process) => {
                    this.replaceProcess(process);
                    target.data('details', process);
                }
            );
        } else {
            this.workflowService.chooseSamplesFromSource(sourceProcess).then(
                (samples) => {
                    if (!samples.length) {
                        // No samples brought over, delete edge
                        addedEntities[0].remove();
                    } else {
                        this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, samples).then(
                            (process) => {
                                this.replaceProcess(process);
                                target.data('details', process);
                            }
                        );
                    }
                }
            );
        }
    }

    replaceProcess(process) {
        let i = _.findIndex(this.processes, p => p.id === process.id);
        if (i !== -1) {
            this.processes.splice(i, 1);
            this.processes.push(process);
        }
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    title: 'Show Details',
                    selector: 'node, edge',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    title: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'add-child',
                    title: 'Add Child',
                    selector: 'node',
                    onClickFunction: (event) => this._addChild(event)
                },
                {
                    id: 'delete-process',
                    title: 'Delete Process',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._deleteProcess(event)
                },
                {
                    id: 'collapse',
                    title: 'Collapse',
                    selector: 'node',
                    onClickFunction: event => this._collapseNode(event)
                },
                {
                    id: 'expand',
                    title: 'Expand',
                    selector: 'node',
                    onClickFunction: event => this._expandNode(event)
                },
                {
                    id: 'hide',
                    title: 'Hide',
                    selector: 'node',
                    onClickFunction: event => this._hideNode(event)
                },
                {
                    id: 'hide-others',
                    title: 'Hide Others',
                    selector: 'node',
                    onClickFunction: event => this._hideOtherNodes(event)
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.experimentsAPI.getProcessForExperiment(this.projectId, this.experimentId, process.id).then(
                p => this.mcshow.processDetailsDialog(p, false).then(
                    updatedProcess => target.data('name', updatedProcess.name)
                )
            );
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, process.id);
    }

    _collapseNode(event) {
        let target = event.cyTarget;
        let nodes = getRemovableChildren(target);
        if (nodes.length) {
            let name = target.data('name');
            target.data('name', `+ ${name}`);
            target.data('collapsed', nodes);
            this.cy.remove(nodes);
        }
    }

    _expandNode(event) {
        let target = event.cyTarget;
        let collapsed = target.data('collapsed');
        if (collapsed) {
            collapsed.restore();
            let name = target.data('name').substring(1);
            target.data('name', name);
            target.data('collapsed', null);
        }
    }

    _hideNode(event) {
        let target = event.cyTarget;
        let nodes = getRemovableChildren(target);
        nodes = nodes.union(target);
        let hidden = this.cy.remove(nodes);
        if (this.hiddenNodes.length) {
            this.hiddenNodes = this.hiddenNodes.union(hidden);
        } else {
            this.hiddenNodes = hidden;
        }
    }

    _hideOtherNodes(event) {
        let target = event.cyTarget;
        let nodesToKeep = getRemovableChildren(target).union(target);
        let nodesToRemove = nodesToKeep.absoluteComplement();
        let hidden = this.cy.remove(nodesToRemove);
        if (this.hiddenNodes.length) {
            this.hiddenNodes = this.hiddenNodes.union(hidden);
        } else {
            this.hiddenNodes = hidden;
        }
    }

    _addChild(event) {
        let process = this.getProcessFromEvent(event);
        this.$mdDialog.show({
            templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-templates-dialog.html',
            controller: SelectProcessTemplateDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            multiple: true,
            locals: {
                parentProcess: process
            }
        });
    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class SelectProcessTemplateDialogController {
    /*@ngInject*/
    constructor($stateParams, $mdDialog, workflowService) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
        this.keepOpen = false;
    }

    addSelectedProcessTemplate(templateId) {
        this.workflowService.addChildProcessFromTemplate(templateId, this.projectId, this.experimentId,
            this.parentProcess, this.keepOpen)
    }

    done() {
        this.$mdDialog.hide();
    }

}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    }
});
<|endoftext|>


<commit_before>class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsAPI, $stateParams, mcstate, $filter,
                $mdDialog, mcshow, workflowState, cyGraph, $timeout) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsAPI = experimentsAPI;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.mcshow = mcshow;
        this.removedNodes = null;
        this.navigator = null;
        this.workflowState = workflowState;
        this.cyGraph = cyGraph;
        this.hiddenNodes = [];
        this.sidebarShowing = true;
        this.$timeout = $timeout;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcstate.subscribe('WORKSPACE$MAXIMIZED', this.myName, (maximized) => {
            this.sidebarShowing = !maximized;
            this.$timeout(() => this.cy.fit(), 300);
        });

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);
        this.mcbus.subscribe('PROCESS$ADD', this.myName, (process) => {
            let node = this.processGraph.createProcessNode(process);
            this.cy.add(node);
            let edges = this.processGraph.createConnectingEdges(process, this.processes);
            if (edges.length) {
                this.cy.add(edges);
            }
            this.processes.push(process);
            this.cy.layout({name: 'dagre', fit: true});
            this.cyGraph.setupQTips(this.cy);
        });
        this.mcbus.subscribe('PROCESS$DELETE', this.myName, (processId) => {
            let nodeToRemove = this.cy.filter(`node[id = "${processId}"]`);
            this.cy.remove(nodeToRemove);
            let i = _.findIndex(this.processes, {id: processId});
            if (i !== -1) {
                this.processes.splice(i, 1);
            }
        });
        this.mcbus.subscribe('EDGE$ADD', this.myName, (source, target) => console.log('EDGE$ADD', source, target));
        this.mcbus.subscribe('EDGE$DELETE', this.myName, (source, target) => console.log('EDGE$DELETE', source, target));
        this.mcbus.subscribe('WORKFLOW$HIDEOTHERS', this.myName, processes => {
            this._addToHidden(this.cyGraph.hideOtherNodesMultiple(this.cy, processes))
        });

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = this.cyGraph.searchProcessesInGraph(this.cy, search, this.processes);
        };

        this.mcbus.subscribe('WORKFLOW$RESTOREHIDDEN', this.myName, () => {
            if (this.hiddenNodes.length) {
                this.hiddenNodes.restore();
                this.hiddenNodes = [];
                this.cy.layout({name: 'dagre', fit: true});
            }
        });

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            this.removedNodes = this.cyGraph.filterBySamples(this.cy, samples, this.processes);
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcbus.leave('WORKFLOW$RESTOREHIDDEN', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        this.mcbus.leave('PROCESS$ADD', this.myName);
        this.mcbus.leave('PROCESS$DELETE', this.myName);
        this.mcbus.leave('EDGE$ADD', this.myName);
        this.mcbus.leave('EDGE$DELETE', this.myName);
        this.mcstate.leave('WORKSPACE$MAXIMIZED', this.myName);
        if (this.navigator) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = this.cyGraph.createGraph(g.elements, 'processesGraph');
        this.cyGraph.setOnClickForExperiment(this.cy, this.projectId, this.experimentId);

        this.ctxMenu = this.setupContextMenus();
        let cb = (source, target, addedEntities) => this._completeFN(source, target, addedEntities);
        this.cyGraph.addEdgeHandles(this.cy, cb);
    }

    _completeFN(source, target, addedEntities) {
        let sourceProcess = source.data('details');
        let targetProcess = target.data('details');
        if (sourceProcess.output_samples.length === 1) {
            this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, sourceProcess.output_samples).then(
                (process) => {
                    this.replaceProcess(process);
                    target.data('details', process);
                    addedEntities[0].data('name', sourceProcess.output_samples[0].name);
                    let names = sourceProcess.output_samples.map(s => s.name).join(',');
                    addedEntities[0].data('details', {
                        samples: sourceProcess.output_samples,
                        names: names
                    });
                    this.cyGraph.setupQTips(this.cy);
                }
            );
        } else {
            this.workflowService.chooseSamplesFromSource(sourceProcess).then(
                (samples) => {
                    if (!samples.length) {
                        // No samples brought over, delete edge
                        addedEntities[0].remove();
                    } else {
                        this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, samples).then(
                            (process) => {
                                this.replaceProcess(process);
                                target.data('details', process);
                                if (samples.length > 1) {
                                    addedEntities[0].data('name', `${samples[0].name} + ${samples.length - 1} more`);
                                } else {
                                    addedEntities[0].data('name', `${samples[0].name}`);
                                }

                                let names = samples.map(s => s.name).join(',');
                                addedEntities[0].data('details', {
                                    samples: samples,
                                    names: names
                                });
                                this.cyGraph.setupQTips(this.cy);
                            }
                        );
                    }
                }
            );
        }
    }

    replaceProcess(process) {
        let i = _.findIndex(this.processes, p => p.id === process.id);
        if (i !== -1) {
            this.processes.splice(i, 1);
            this.processes.push(process);
        }
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    content: 'Show Details',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    content: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'add-child',
                    content: 'Add Child',
                    selector: 'node',
                    onClickFunction: (event) => this._addChild(event)
                },
                {
                    id: 'delete-process',
                    content: 'Delete Process',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._deleteProcess(event)
                },
                {
                    id: 'collapse',
                    content: 'Collapse',
                    selector: 'node',
                    onClickFunction: event => this.cyGraph.collapseNode(this.cy, event)
                },
                {
                    id: 'expand',
                    content: 'Expand',
                    selector: 'node',
                    onClickFunction: event => this.cyGraph.expandNode(event)
                },
                {
                    id: 'hide',
                    content: 'Hide',
                    selector: 'node',
                    onClickFunction: event => this._addToHidden(this.cyGraph.hideNode(this.cy, event))
                },
                {
                    id: 'hide-others',
                    content: 'Hide Others',
                    selector: 'node',
                    onClickFunction: event => this._addToHidden(this.cyGraph.hideOtherNodes(this.cy, event))
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.experimentsAPI.getProcessForExperiment(this.projectId, this.experimentId, process.id).then(
                p => this.mcshow.processDetailsDialog(p, false).then(
                    updatedProcess => target.data('name', updatedProcess.name)
                )
            );
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, process.id);
    }

    _addToHidden(hidden) {
        if (this.hiddenNodes.length) {
            this.hiddenNodes = this.hiddenNodes.union(hidden);
        } else {
            this.hiddenNodes = hidden;
        }
    }

    _addChild(event) {
        let process = this.getProcessFromEvent(event);
        this.$mdDialog.show({
            templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-templates-dialog.html',
            controller: SelectProcessTemplateDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            multiple: true,
            locals: {
                parentProcess: process
            }
        });
    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class SelectProcessTemplateDialogController {
    /*@ngInject*/
    constructor($stateParams, $mdDialog, workflowService) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
        this.keepOpen = false;
    }

    addSelectedProcessTemplate(templateId) {
        this.workflowService.addChildProcessFromTemplate(templateId, this.projectId, this.experimentId,
            this.parentProcess, this.keepOpen)
    }

    done() {
        this.$mdDialog.hide();
    }

}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    }
});
<commit_msg>Ensure all subscriptions are cleaned up in $onDestroy. Some formatting<commit_after>class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsAPI, $stateParams, mcstate, $filter,
                $mdDialog, mcshow, workflowState, cyGraph, $timeout) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsAPI = experimentsAPI;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.mcshow = mcshow;
        this.removedNodes = null;
        this.navigator = null;
        this.workflowState = workflowState;
        this.cyGraph = cyGraph;
        this.hiddenNodes = [];
        this.sidebarShowing = true;
        this.$timeout = $timeout;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcstate.subscribe('WORKSPACE$MAXIMIZED', this.myName, (maximized) => {
            this.sidebarShowing = !maximized;
            this.$timeout(() => this.cy.fit(), 300);
        });

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);
        this.mcbus.subscribe('PROCESS$ADD', this.myName, (process) => {
            let node = this.processGraph.createProcessNode(process);
            this.cy.add(node);
            let edges = this.processGraph.createConnectingEdges(process, this.processes);
            if (edges.length) {
                this.cy.add(edges);
            }
            this.processes.push(process);
            this.cy.layout({name: 'dagre', fit: true});
            this.cyGraph.setupQTips(this.cy);
        });
        this.mcbus.subscribe('PROCESS$DELETE', this.myName, (processId) => {
            let nodeToRemove = this.cy.filter(`node[id = "${processId}"]`);
            this.cy.remove(nodeToRemove);
            let i = _.findIndex(this.processes, {id: processId});
            if (i !== -1) {
                this.processes.splice(i, 1);
            }
        });
        this.mcbus.subscribe('EDGE$ADD', this.myName, (source, target) => console.log('EDGE$ADD', source, target));
        this.mcbus.subscribe('EDGE$DELETE', this.myName, (source, target) => console.log('EDGE$DELETE', source, target));
        this.mcbus.subscribe('WORKFLOW$HIDEOTHERS', this.myName, processes => {
            this._addToHidden(this.cyGraph.hideOtherNodesMultiple(this.cy, processes))
        });

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = this.cyGraph.searchProcessesInGraph(this.cy, search, this.processes);
        });

        this.mcbus.subscribe('WORKFLOW$RESTOREHIDDEN', this.myName, () => {
            if (this.hiddenNodes.length) {
                this.hiddenNodes.restore();
                this.hiddenNodes = [];
                this.cy.layout({name: 'dagre', fit: true});
            }
        });

        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            this.removedNodes = this.cyGraph.filterBySamples(this.cy, samples, this.processes);
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcbus.leave('WORKFLOW$RESTOREHIDDEN', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        this.mcbus.leave('WORKFLOW$FILTER$BYSAMPLES', this.myName);
        this.mcbus.leave('PROCESS$ADD', this.myName);
        this.mcbus.leave('PROCESS$DELETE', this.myName);
        this.mcbus.leave('EDGE$ADD', this.myName);
        this.mcbus.leave('EDGE$DELETE', this.myName);
        this.mcstate.leave('WORKSPACE$MAXIMIZED', this.myName);
        this.mcbus.leave('WORKFLOW$HIDEOTHERS', this.myName);
        this.mcbus.leave('WORKFLOW$NAVIGATOR', this.myName);
        this.mcbus.leave('WORKFLOW$RESTOREHIDDEN', this.myName);
        if (this.navigator) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = this.cyGraph.createGraph(g.elements, 'processesGraph');
        this.cyGraph.setOnClickForExperiment(this.cy, this.projectId, this.experimentId);

        this.ctxMenu = this.setupContextMenus();
        let cb = (source, target, addedEntities) => this._completeFN(source, target, addedEntities);
        this.cyGraph.addEdgeHandles(this.cy, cb);
    }

    _completeFN(source, target, addedEntities) {
        let sourceProcess = source.data('details');
        let targetProcess = target.data('details');
        if (sourceProcess.output_samples.length === 1) {
            this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, sourceProcess.output_samples).then(
                (process) => {
                    this.replaceProcess(process);
                    target.data('details', process);
                    addedEntities[0].data('name', sourceProcess.output_samples[0].name);
                    let names = sourceProcess.output_samples.map(s => s.name).join(',');
                    addedEntities[0].data('details', {
                        samples: sourceProcess.output_samples,
                        names: names
                    });
                    this.cyGraph.setupQTips(this.cy);
                }
            );
        } else {
            this.workflowService.chooseSamplesFromSource(sourceProcess).then(
                (samples) => {
                    if (!samples.length) {
                        // No samples brought over, delete edge
                        addedEntities[0].remove();
                    } else {
                        this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, samples).then(
                            (process) => {
                                this.replaceProcess(process);
                                target.data('details', process);
                                if (samples.length > 1) {
                                    addedEntities[0].data('name', `${samples[0].name} + ${samples.length - 1} more`);
                                } else {
                                    addedEntities[0].data('name', `${samples[0].name}`);
                                }

                                let names = samples.map(s => s.name).join(',');
                                addedEntities[0].data('details', {
                                    samples: samples,
                                    names: names
                                });
                                this.cyGraph.setupQTips(this.cy);
                            }
                        );
                    }
                }
            );
        }
    }

    replaceProcess(process) {
        let i = _.findIndex(this.processes, p => p.id === process.id);
        if (i !== -1) {
            this.processes.splice(i, 1);
            this.processes.push(process);
        }
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    content: 'Show Details',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    content: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'add-child',
                    content: 'Add Child',
                    selector: 'node',
                    onClickFunction: (event) => this._addChild(event)
                },
                {
                    id: 'delete-process',
                    content: 'Delete Process',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._deleteProcess(event)
                },
                {
                    id: 'collapse',
                    content: 'Collapse',
                    selector: 'node',
                    onClickFunction: event => this.cyGraph.collapseNode(this.cy, event)
                },
                {
                    id: 'expand',
                    content: 'Expand',
                    selector: 'node',
                    onClickFunction: event => this.cyGraph.expandNode(event)
                },
                {
                    id: 'hide',
                    content: 'Hide',
                    selector: 'node',
                    onClickFunction: event => this._addToHidden(this.cyGraph.hideNode(this.cy, event))
                },
                {
                    id: 'hide-others',
                    content: 'Hide Others',
                    selector: 'node',
                    onClickFunction: event => this._addToHidden(this.cyGraph.hideOtherNodes(this.cy, event))
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.experimentsAPI.getProcessForExperiment(this.projectId, this.experimentId, process.id).then(
                p => this.mcshow.processDetailsDialog(p, false).then(
                    updatedProcess => target.data('name', updatedProcess.name)
                )
            );
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, process.id);
    }

    _addToHidden(hidden) {
        if (this.hiddenNodes.length) {
            this.hiddenNodes = this.hiddenNodes.union(hidden);
        } else {
            this.hiddenNodes = hidden;
        }
    }

    _addChild(event) {
        let process = this.getProcessFromEvent(event);
        this.$mdDialog.show({
            templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-templates-dialog.html',
            controller: SelectProcessTemplateDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            multiple: true,
            locals: {
                parentProcess: process
            }
        });
    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class SelectProcessTemplateDialogController {
    /*@ngInject*/
    constructor($stateParams, $mdDialog, workflowService) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
        this.keepOpen = false;
    }

    addSelectedProcessTemplate(templateId) {
        this.workflowService.addChildProcessFromTemplate(templateId, this.projectId, this.experimentId,
            this.parentProcess, this.keepOpen)
    }

    done() {
        this.$mdDialog.hide();
    }

}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    }
});
<|endoftext|>


<commit_before>const {
  stat,
  readdir,
} = require('fs').promises

const {
  map,
  each,
  some,
} = require('async')

const decorators = { }

const load = async spec => {
  const initialize = require('./Initializer')

  const root   = `${process.cwd()}/.madul`
  const status = await stat(root)

  if (status.isDirectory()) {
    await each(await readdir(root), async node => {
      const path  = `${root}/${node}`
      const nstat = await stat(path)

      if (nstat.isFile()) {
        const bundle = require(path)

        if (bundle.maduls.includes(spec))
          decorators[spec] = await map(bundle.decorators, async d => await initialize(d))
      }
    })
  }
}

const execute = async ({ spec, method, mode, params, output }) => {
  if (Array.isArray(decorators[spec]))
    return await some(decorators[spec], async d => {
      if (typeof d[mode] === 'function') {
        const args = { madul: spec, method }

        if (mode === 'before')
          args.params = params
        else if (mode === 'after')
          args.output = output

        return await d[mode](args) !== true
      }
    }) === false
  else
    return true
}

exports.load    = load
exports.execute = execute<commit_msg>Implemented support for multiple bundles<commit_after>const {
  stat,
  readdir,
} = require('fs').promises

const {
  each,
  some,
} = require('async')

const decorators = { }

const load = async spec => {
  const initialize = require('./Initializer')

  const root   = `${process.cwd()}/.madul`
  const status = await stat(root)

  decorators[spec] = [ ]

  if (status.isDirectory()) {
    await each(await readdir(root), async node => {
      const path  = `${root}/${node}`
      const nstat = await stat(path)

      if (nstat.isFile() && path.endsWith('bundle.js')) {
        const bundle = require(path)

        if (bundle.maduls.includes(spec))
          await each(bundle.decorators, async d => {
            decorators[spec].push(await initialize(d))
          })
      }
    })
  }
}

const execute = async ({ spec, method, mode, params, output }) => {
  if (Array.isArray(decorators[spec]))
    return await some(decorators[spec], async d => {
      if (typeof d[mode] === 'function') {
        const args = { madul: spec, method }

        if (mode === 'before')
          args.params = params
        else if (mode === 'after')
          args.output = output

        return await d[mode](args) !== true
      }
    }) === false
  else
    return true
}

exports.load    = load
exports.execute = execute<|endoftext|>


<commit_before>/**
 * Copyright 2019 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {helper, assert} = require('./helper');
const {Events} = require('./Events');
const {TimeoutError} = require('./Errors');

class LifecycleWatcher {
  /**
   * @param {!Puppeteer.FrameManager} frameManager
   * @param {!Puppeteer.Frame} frame
   * @param {string|!Array<string>} waitUntil
   * @param {number} timeout
   */
  constructor(frameManager, frame, waitUntil, timeout) {
    if (Array.isArray(waitUntil))
      waitUntil = waitUntil.slice();
    else if (typeof waitUntil === 'string')
      waitUntil = [waitUntil];
    this._expectedLifecycle = waitUntil.map(value => {
      const protocolEvent = puppeteerToProtocolLifecycle[value];
      assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
      return protocolEvent;
    });

    this._frameManager = frameManager;
    this._frame = frame;
    this._initialLoaderId = frame._loaderId;
    this._timeout = timeout;
    /** @type {?Puppeteer.Request} */
    this._navigationRequest = null;
    this._eventListeners = [
      helper.addEventListener(frameManager._client, Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
      helper.addEventListener(this._frameManager, Events.FrameManager.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
      helper.addEventListener(this._frameManager, Events.FrameManager.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
      helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)),
      helper.addEventListener(this._frameManager.networkManager(), Events.NetworkManager.Request, this._onRequest.bind(this)),
    ];

    this._sameDocumentNavigationPromise = new Promise(fulfill => {
      this._sameDocumentNavigationCompleteCallback = fulfill;
    });

    this._lifecyclePromise = new Promise(fulfill => {
      this._lifecycleCallback = fulfill;
    });

    this._newDocumentNavigationPromise = new Promise(fulfill => {
      this._newDocumentNavigationCompleteCallback = fulfill;
    });

    this._timeoutPromise = this._createTimeoutPromise();
    this._terminationPromise = new Promise(fulfill => {
      this._terminationCallback = fulfill;
    });
    this._checkLifecycleComplete();
  }

  /**
   * @param {!Puppeteer.Request} request
   */
  _onRequest(request) {
    if (request.frame() !== this._frame || !request.isNavigationRequest())
      return;
    this._navigationRequest = request;
  }

  /**
   * @param {!Puppeteer.Frame} frame
   */
  _onFrameDetached(frame) {
    if (this._frame === frame) {
      this._terminationCallback.call(null, new Error('Navigating frame was detached'));
      return;
    }
    this._checkLifecycleComplete();
  }

  /**
   * @return {?Puppeteer.Response}
   */
  navigationResponse() {
    return this._navigationRequest ? this._navigationRequest.response() : null;
  }

  /**
   * @param {!Error} error
   */
  _terminate(error) {
    this._terminationCallback.call(null, error);
  }

  /**
   * @return {!Promise<?Error>}
   */
  sameDocumentNavigationPromise() {
    return this._sameDocumentNavigationPromise;
  }

  /**
   * @return {!Promise<?Error>}
   */
  newDocumentNavigationPromise() {
    return this._newDocumentNavigationPromise;
  }

  /**
   * @return {!Promise}
   */
  lifecyclePromise() {
    return this._lifecyclePromise;
  }

  /**
   * @return {!Promise<?Error>}
   */
  timeoutOrTerminationPromise() {
    return Promise.race([this._timeoutPromise, this._terminationPromise]);
  }

  /**
   * @return {!Promise<?Error>}
   */
  _createTimeoutPromise() {
    if (!this._timeout)
      return new Promise(() => {});
    const errorMessage = 'Navigation timeout of ' + this._timeout + ' ms exceeded';
    return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
        .then(() => new TimeoutError(errorMessage));
  }

  /**
   * @param {!Puppeteer.Frame} frame
   */
  _navigatedWithinDocument(frame) {
    if (frame !== this._frame)
      return;
    this._hasSameDocumentNavigation = true;
    this._checkLifecycleComplete();
  }

  _checkLifecycleComplete() {
    // We expect navigation to commit.
    if (!checkLifecycle(this._frame, this._expectedLifecycle))
      return;
    this._lifecycleCallback();
    if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
      return;
    if (this._hasSameDocumentNavigation)
      this._sameDocumentNavigationCompleteCallback();
    if (this._frame._loaderId !== this._initialLoaderId)
      this._newDocumentNavigationCompleteCallback();

    /**
     * @param {!Puppeteer.Frame} frame
     * @param {!Array<string>} expectedLifecycle
     * @return {boolean}
     */
    function checkLifecycle(frame, expectedLifecycle) {
      for (const event of expectedLifecycle) {
        if (!frame._lifecycleEvents.has(event))
          return false;
      }
      for (const child of frame.childFrames()) {
        if (!checkLifecycle(child, expectedLifecycle))
          return false;
      }
      return true;
    }
  }

  dispose() {
    helper.removeEventListeners(this._eventListeners);
    clearTimeout(this._maximumTimer);
  }
}

const puppeteerToProtocolLifecycle = {
  'load': 'load',
  'domcontentloaded': 'DOMContentLoaded',
  'networkidle0': 'networkIdle',
  'networkidle2': 'networkAlmostIdle',
};

module.exports = {LifecycleWatcher};
<commit_msg>chore: use map instead of plain object (#5068)<commit_after>/**
 * Copyright 2019 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const {helper, assert} = require('./helper');
const {Events} = require('./Events');
const {TimeoutError} = require('./Errors');

class LifecycleWatcher {
  /**
   * @param {!Puppeteer.FrameManager} frameManager
   * @param {!Puppeteer.Frame} frame
   * @param {string|!Array<string>} waitUntil
   * @param {number} timeout
   */
  constructor(frameManager, frame, waitUntil, timeout) {
    if (Array.isArray(waitUntil))
      waitUntil = waitUntil.slice();
    else if (typeof waitUntil === 'string')
      waitUntil = [waitUntil];
    this._expectedLifecycle = waitUntil.map(value => {
      const protocolEvent = puppeteerToProtocolLifecycle.get(value);
      assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
      return protocolEvent;
    });

    this._frameManager = frameManager;
    this._frame = frame;
    this._initialLoaderId = frame._loaderId;
    this._timeout = timeout;
    /** @type {?Puppeteer.Request} */
    this._navigationRequest = null;
    this._eventListeners = [
      helper.addEventListener(frameManager._client, Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
      helper.addEventListener(this._frameManager, Events.FrameManager.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
      helper.addEventListener(this._frameManager, Events.FrameManager.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
      helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)),
      helper.addEventListener(this._frameManager.networkManager(), Events.NetworkManager.Request, this._onRequest.bind(this)),
    ];

    this._sameDocumentNavigationPromise = new Promise(fulfill => {
      this._sameDocumentNavigationCompleteCallback = fulfill;
    });

    this._lifecyclePromise = new Promise(fulfill => {
      this._lifecycleCallback = fulfill;
    });

    this._newDocumentNavigationPromise = new Promise(fulfill => {
      this._newDocumentNavigationCompleteCallback = fulfill;
    });

    this._timeoutPromise = this._createTimeoutPromise();
    this._terminationPromise = new Promise(fulfill => {
      this._terminationCallback = fulfill;
    });
    this._checkLifecycleComplete();
  }

  /**
   * @param {!Puppeteer.Request} request
   */
  _onRequest(request) {
    if (request.frame() !== this._frame || !request.isNavigationRequest())
      return;
    this._navigationRequest = request;
  }

  /**
   * @param {!Puppeteer.Frame} frame
   */
  _onFrameDetached(frame) {
    if (this._frame === frame) {
      this._terminationCallback.call(null, new Error('Navigating frame was detached'));
      return;
    }
    this._checkLifecycleComplete();
  }

  /**
   * @return {?Puppeteer.Response}
   */
  navigationResponse() {
    return this._navigationRequest ? this._navigationRequest.response() : null;
  }

  /**
   * @param {!Error} error
   */
  _terminate(error) {
    this._terminationCallback.call(null, error);
  }

  /**
   * @return {!Promise<?Error>}
   */
  sameDocumentNavigationPromise() {
    return this._sameDocumentNavigationPromise;
  }

  /**
   * @return {!Promise<?Error>}
   */
  newDocumentNavigationPromise() {
    return this._newDocumentNavigationPromise;
  }

  /**
   * @return {!Promise}
   */
  lifecyclePromise() {
    return this._lifecyclePromise;
  }

  /**
   * @return {!Promise<?Error>}
   */
  timeoutOrTerminationPromise() {
    return Promise.race([this._timeoutPromise, this._terminationPromise]);
  }

  /**
   * @return {!Promise<?Error>}
   */
  _createTimeoutPromise() {
    if (!this._timeout)
      return new Promise(() => {});
    const errorMessage = 'Navigation timeout of ' + this._timeout + ' ms exceeded';
    return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
        .then(() => new TimeoutError(errorMessage));
  }

  /**
   * @param {!Puppeteer.Frame} frame
   */
  _navigatedWithinDocument(frame) {
    if (frame !== this._frame)
      return;
    this._hasSameDocumentNavigation = true;
    this._checkLifecycleComplete();
  }

  _checkLifecycleComplete() {
    // We expect navigation to commit.
    if (!checkLifecycle(this._frame, this._expectedLifecycle))
      return;
    this._lifecycleCallback();
    if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
      return;
    if (this._hasSameDocumentNavigation)
      this._sameDocumentNavigationCompleteCallback();
    if (this._frame._loaderId !== this._initialLoaderId)
      this._newDocumentNavigationCompleteCallback();

    /**
     * @param {!Puppeteer.Frame} frame
     * @param {!Array<string>} expectedLifecycle
     * @return {boolean}
     */
    function checkLifecycle(frame, expectedLifecycle) {
      for (const event of expectedLifecycle) {
        if (!frame._lifecycleEvents.has(event))
          return false;
      }
      for (const child of frame.childFrames()) {
        if (!checkLifecycle(child, expectedLifecycle))
          return false;
      }
      return true;
    }
  }

  dispose() {
    helper.removeEventListeners(this._eventListeners);
    clearTimeout(this._maximumTimer);
  }
}

const puppeteerToProtocolLifecycle = new Map([
  ['load', 'load'],
  ['domcontentloaded', 'DOMContentLoaded'],
  ['networkidle0', 'networkIdle'],
  ['networkidle2', 'networkAlmostIdle'],
]);

module.exports = {LifecycleWatcher};
<|endoftext|>


<commit_before>/**
 * PepipostLib
 *
 * This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io )
 */

'use strict';

const BaseModel = require('./BaseModel');

/**
 * Creates an instance of Attribute
 */
class Attribute extends BaseModel {
    /**
     * @constructor
     * @param   {Object}  obj    The object passed to constructor
     */
    constructor(obj) {
        super(obj);
        if (obj === undefined || obj === null) return;
        this.myname = this.constructor.getValue(obj.myname);
        this.age = this.constructor.getValue(obj.age);
    }

    /**
     * Function containing information about the fields of this model
     * @return   {array}   Array of objects containing information about the fields
     */
    static mappingInfo() {
        return super.mappingInfo().concat([
            { name: 'myname', realName: 'myname' },
            { name: 'age', realName: 'age' },
        ]);
    }

    /**
     * Function containing information about discriminator values
     * mapped with their corresponding model class names
     *
     * @return   {object}  Object containing Key-Value pairs mapping discriminator
     *                     values with their corresponding model classes
     */
    static discriminatorMap() {
        return {};
    }
}

module.exports = Attribute;
<commit_msg>fixes issue #2<commit_after>/**
 * PepipostLib
 *
 * This file was automatically generated by APIMATIC v2.0 ( https://apimatic.io )
 */

'use strict';

const BaseModel = require('./BaseModel');

/**
 * Creates an instance of Attribute
 */
class Attribute extends BaseModel {
    /**
     * @constructor
     * @param   {Object}  obj    The object passed to constructor
     */
    constructor(obj) {
        super(obj);
        if (obj === undefined || obj === null) return;
        const keys = Object.keys(obj);

        for (const key of keys) {
            if (obj.hasOwnProperty(key)) {
                this[key] = this.constructor.getValue(obj[key]);
            }
        }
    }

    /**
     * Function containing information about the fields of this model
     * @return   {array}   Array of objects containing information about the fields
     */
    static mappingInfo() {
        return super
            .mappingInfo()
            .concat([{ name: 'myname', realName: 'myname' }, { name: 'age', realName: 'age' }]);
    }

    /**
     * Function containing information about discriminator values
     * mapped with their corresponding model class names
     *
     * @return   {object}  Object containing Key-Value pairs mapping discriminator
     *                     values with their corresponding model classes
     */
    static discriminatorMap() {
        return {};
    }
}

module.exports = Attribute;
<|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const path = require("path");
const NORMALIZE_SLASH_DIRECTION_REGEXP = /\\/g;
const PATH_CHARS_REGEXP = /[-[\]{}()*+?.,\\^$|#\s]/g;
const SEPERATOR_REGEXP = /[\/\\]$/;
const FRONT_OR_BACK_BANG_REGEXP = /^!|!$/;

const normalizeBackSlashDirection = (request) => {
	return request.replace(NORMALIZE_SLASH_DIRECTION_REGEXP, "/");
};

const shortenPath = (path) => {
	return path.replace(PATH_CHARS_REGEXP, "\\$&");
};

const createRegExpForTypeWith = (regexpTypePartial) => {
	return new RegExp(`^${regexpTypePartial}|(!)${regexpTypePartial}`, "g");
};

class RequestShortener {
	constructor(directory) {
		directory = normalizeBackSlashDirection(directory);
		if(SEPERATOR_REGEXP.test(directory)) directory = directory.substr(0, directory.length - 1);

		if(directory) {
			const currentDirectoryRegExpString = shortenPath(directory);
			this.currentDirectoryRegExp = createRegExpForTypeWith(currentDirectoryRegExpString);
		}

		const dirname = path.dirname(directory);
		const endsWithSeperator = SEPERATOR_REGEXP.test(dirname);
		const parentDirectory = endsWithSeperator ? dirname.substr(0, dirname.length - 1) : dirname;
		if(parentDirectory && parentDirectory !== directory) {
			const parentDirectoryRegExpString = shortenPath(parentDirectory);
			this.parentDirectoryRegExp = createRegExpForTypeWith(parentDirectoryRegExpString);
		}

		if(__dirname.length >= 2) {
			const buildins = normalizeBackSlashDirection(path.join(__dirname, ".."));
			const buildinsAsModule = this.currentDirectoryRegExp && this.currentDirectoryRegExp.test(buildins);
			this.buildinsAsModule = buildinsAsModule;
			const buildinsRegExpString = shortenPath(buildins);
			this.buildinsRegExp = createRegExpForTypeWith(buildinsRegExpString);
		}

		this.indexJsRegExp = /\/index.js(!|\?|\(query\))/g;
	}

	shorten(request) {
		if(!request) return request;
		request = normalizeBackSlashDirection(request);
		if(this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		if(this.currentDirectoryRegExp)
			request = request.replace(this.currentDirectoryRegExp, "!.");
		if(this.parentDirectoryRegExp)
			request = request.replace(this.parentDirectoryRegExp, "!..");
		if(!this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		request = request.replace(this.indexJsRegExp, "$1");
		return request.replace(FRONT_OR_BACK_BANG_REGEXP, "");
	}
}

module.exports = RequestShortener;
<commit_msg>feat(perf): combine two functions, fix found bug<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const path = require("path");
const NORMALIZE_SLASH_DIRECTION_REGEXP = /\\/g;
const PATH_CHARS_REGEXP = /[-[\]{}()*+?.,\\^$|#\s]/g;
const SEPERATOR_REGEXP = /[\/\\]$/;
const FRONT_OR_BACK_BANG_REGEXP = /^!|!$/g;

const normalizeBackSlashDirection = (request) => {
	return request.replace(NORMALIZE_SLASH_DIRECTION_REGEXP, "/");
};

const createRegExpForPath = (path) => {
	const regexpTypePartial = path.replace(PATH_CHARS_REGEXP, "\\$&");
	return new RegExp(`(^|!)${regexpTypePartial}`, "g");
};

class RequestShortener {
	constructor(directory) {
		directory = normalizeBackSlashDirection(directory);
		if(SEPERATOR_REGEXP.test(directory)) directory = directory.substr(0, directory.length - 1);

		if(directory) {
			this.currentDirectoryRegExp = createRegExpForPath(directory);
		}

		const dirname = path.dirname(directory);
		const endsWithSeperator = SEPERATOR_REGEXP.test(dirname);
		const parentDirectory = endsWithSeperator ? dirname.substr(0, dirname.length - 1) : dirname;
		if(parentDirectory && parentDirectory !== directory) {
			this.parentDirectoryRegExp = createRegExpForPath(parentDirectory);
		}

		if(__dirname.length >= 2) {
			const buildins = normalizeBackSlashDirection(path.join(__dirname, ".."));
			const buildinsAsModule = this.currentDirectoryRegExp && this.currentDirectoryRegExp.test(buildins);
			this.buildinsAsModule = buildinsAsModule;
			this.buildinsRegExp = createRegExpForPath(buildins);
		}

		this.indexJsRegExp = /\/index.js(!|\?|\(query\))/g;
	}

	shorten(request) {
		if(!request) return request;
		request = normalizeBackSlashDirection(request);
		if(this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		if(this.currentDirectoryRegExp)
			request = request.replace(this.currentDirectoryRegExp, "!.");
		if(this.parentDirectoryRegExp)
			request = request.replace(this.parentDirectoryRegExp, "!..");
		if(!this.buildinsAsModule && this.buildinsRegExp)
			request = request.replace(this.buildinsRegExp, "!(webpack)");
		request = request.replace(this.indexJsRegExp, "$1");
		return request.replace(FRONT_OR_BACK_BANG_REGEXP, "");
	}
}

module.exports = RequestShortener;
<|endoftext|>


<commit_before>'use strict';
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');

function staticHttpServer(root = process.cwd(), port = 23333, hostname = '0.0.0.0') {

    const server = http.createServer(onRequest);

    server.listen(port, hostname, () => {
        console.log(`The static http server is running at http://${hostname}:${port}/`);
    });

    function onRequest(req, res) {
        const name = decodeURI(url.parse(req.url).pathname);//The path that the client visits
        let pathname = path.join(root, name);//The true path on server
        let mimetype = mime.lookup(name);
        fs.stat(pathname, (err, stats) => {
            if (err) {
                solveException('404');
            } else {
                if (stats.isDirectory()) {
                    visitDirectory((err, data) => {
                        if (err) {
                            solveException(err);
                        }else {
                            writeAll(200, data, mimetype);
                        }
                    });
                } else {
                    fileOpen(pathname, (err, data) => {
                        if (err) {
                            solveException(err);
                        } else {
                            writeAll(200, data, mimetype);
                        }
                    });
                }
            }
        });

        function solveException(err) {
            switch (err) {
                case '404':
                    writeAll(404, `404 Not Found: ${name}\n`, 'text/html');
                    break;
                default:
                    writeAll(500, '500 Error\n', 'text/html');
            }
        }

        function directoryIndex(callback) {
            //callback(err, path)
            //The path is the index file name
            const indexFileNames = ['index.html', 'index.htm', 'default.html', 'default.htm'];
            const hasIndex = indexFileNames.some((indexFileName) => {
                fs.access(path.join(pathname, indexFileName), (err) => {
                    if (err) {
                        return false;
                    } else {
                        callback(undefined, path.join(pathname, indexFileName));
                        return true;
                    }
                });
            });
            if (!hasIndex) {
                callback('Index File Not Found');
            }
        }

        function visitDirectory(callback) {
            //callback(err, data)
            directoryIndex((err, path) => {
                if (err) {
                    directoryList((err, data) => {
                        if (err) {
                            callback(err);
                        } else {
                            callback(undefined, data);
                        }
                    });
                } else {
                    fileOpen(path, (err, data) => {
                        if (err) {
                            callback(err);
                        } else {
                            callback(undefined, data);
                        }
                    });
                }
            });
        }

        function directoryList(callback) {
            //callback(err, data)
            //List the files in the directory
            fs.readdir(pathname, (err, files) => {
                if (err) {
                    callback('500');
                } else {
                    files.unshift('..');
                    let str = '';
                    files.forEach((file) => {
                        const filePath = path.join(name, file);
                        const slash = fs.statSync(path.join(root, filePath)).isDirectory() ? '/' : '';
                        str += `<p><a href="${encodeURI(filePath)}">${file}${slash}</a></p>\n`;
                    });
                    mimetype = 'text/html';
                    callback(undefined, str);
                }
            });
        }

        function fileOpen(file, callback) {
            //callback(err, data)
            mimetype = mime.lookup(file);
            fs.readFile(file, (err, data) => {
                if (err) {
                    callback('500');
                } else {
                    callback(undefined, data);
                }
            });
        }

        function writeAll(statusCode, data, mimetype) {
            res.writeHead(statusCode, {'Content-Type': `${mimetype};charset=utf-8`});
            res.write(data);
            res.end();
        }
    }
}
module.exports = staticHttpServer;
<commit_msg>Update<commit_after>'use strict';
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');

function staticHttpServer(root = process.cwd(), port = 23333, hostname = '0.0.0.0') {

    const server = http.createServer(onRequest);

    server.listen(port, hostname, () => {
        console.log(`The static http server is running at http://${hostname}:${port}/`);
    });

    function onRequest(req, res) {
        const name = decodeURI(url.parse(req.url).pathname);//The path that the client visits
        let pathname = path.join(root, name);//The true path on server
        fs.stat(pathname, (err, stats) => {
            if (err) {
                solveException('404');
            } else {
                if (stats.isDirectory()) {
                    visitDirectory((err, data, mimeType) => {
                        if (err) {
                            solveException(err);
                        }else {
                            writeAll(200, data, mimeType);
                        }
                    });
                } else {
                    fileOpen(pathname, (err, data) => {
                        if (err) {
                            solveException(err);
                        } else {
                            writeAll(200, data, mime.lookup(name));
                        }
                    });
                }
            }
        });

        function solveException(err) {
            switch (err) {
                case '404':
                    writeAll(404, `404 Not Found: ${name}\n`, 'text/html');
                    break;
                default:
                    writeAll(500, '500 Error\n', 'text/html');
            }
        }

        function directoryIndex(callback) {
            //callback(err, path)
            //The path is the index file name
            const indexFileNames = ['index.html', 'index.htm', 'default.html', 'default.htm'];
            const hasIndex = indexFileNames.some((indexFileName) => {
                fs.access(path.join(pathname, indexFileName), (err) => {
                    if (err) {
                        return false;
                    } else {
                        callback(undefined, path.join(pathname, indexFileName));
                        return true;
                    }
                });
            });
            if (!hasIndex) {
                callback('Index File Not Found');
            }
        }

        function visitDirectory(callback) {
            //callback(err, data, mimeType)
            directoryIndex((err, path) => {
                if (err) {
                    directoryList((err, data) => {
                        if (err) {
                            callback(err);
                        } else {
                            callback(undefined, data, 'text/html');
                        }
                    });
                } else {
                    fileOpen(path, (err, data) => {
                        if (err) {
                            callback(err);
                        } else {
                            callback(undefined, data, mime.lookup(path));
                        }
                    });
                }
            });
        }

        function directoryList(callback) {
            //callback(err, data)
            //List the files in the directory
            fs.readdir(pathname, (err, files) => {
                if (err) {
                    callback('500');
                } else {
                    files.unshift('..');
                    let str = '';
                    files.forEach((file) => {
                        const filePath = path.join(name, file);
                        const slash = fs.statSync(path.join(root, filePath)).isDirectory() ? '/' : '';
                        str += `<p><a href="${encodeURI(filePath)}">${file}${slash}</a></p>\n`;
                    });
                    callback(undefined, str);
                }
            });
        }

        function fileOpen(file, callback) {
            //callback(err, data)
            fs.readFile(file, (err, data) => {
                if (err) {
                    callback('500');
                } else {
                    callback(undefined, data);
                }
            });
        }

        function writeAll(statusCode, data, mimeType) {
            res.writeHead(statusCode, {'Content-Type': `${mimeType};charset=utf-8`});
            res.write(data);
            res.end();
        }
    }
}
module.exports = staticHttpServer;
<|endoftext|>