<commit_before>import $ from "jquery"
import React from "react"
import ReactDOM from "react-dom"
import isMobile from "../isMobile"
import _ from "underscore"
import whereDoesCharComeFrom from "../whereDoesCharComeFrom"
import getRootOriginAtChar from "../getRootOriginAtChar"
import { OriginPath, PreviewElementMarker, SelectedElementMarker } from "../ui/ui"
import {disableTracing, enableTracing, disableEventListeners, enableEventListeners,runFunctionWithTracingDisabled} from "../tracing/tracing"
import RoundTripMessageWrapper from "../RoundTripMessageWrapper"
import getCodeFilePath from "./getCodeFilePath"

var elementsByElementId = {}

export default function showFromJSSidebar(resolveFrameWorker){
    disableTracing()

    var container = document.createElement("div")
    container.className = "fromjs-outer-container"

    var container2 = document.createElement("div")
    container2.id = "fromjs-sidebar"

    var sidebarIframe = document.createElement("iframe")
    sidebarIframe.setAttribute("style", "width: 100%; height: 100%; box-shadow: 0px 0px 20px gray;border: none")

    container2.appendChild(sidebarIframe)
    container.appendChild(container2)

    document.body.appendChild(container)
    var rootUrl = "/fromjs-internals/"
    if (window.fromJSInternalsRoot){
        rootUrl = window.fromJSInternalsRoot
    }
    var cssUrl = rootUrl + "fromjs.css";
    var jsUrl = rootUrl + "inspector.js";
    sidebarIframe.contentDocument.write(`
        <!doctype html>

Loading Inspector UI...

                <link rel="stylesheet" href="${cssUrl}">

    `)

    var elementMarkerContainer = document.createElement("div")
    container.appendChild(elementMarkerContainer)

    var previewElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(previewElementMarkerContainer)

    var selectedElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(selectedElementMarkerContainer)

    function shouldHandle(e) {
        if ($(e.target).closest("#fromjs-sidebar").length !== 0) {
            return false
        }
        if ($(e.target).is("html, body")) {
            return false
        }
        return true
    }

    disableEventListeners()

    var inspectorPage = new RoundTripMessageWrapper(sidebarIframe.contentWindow, "Inspected App/Sidebar")
    inspectorPage.beforePostMessage = disableTracing
    inspectorPage.afterPostMessage = enableTracing

    var currentSelectedElement = null;
    var currentPreviewedElement = null;
    var nonElementOriginSelected = false;
    // We already disable event listeners created with addEventListener
    // but not e.g. onclick attributes, or prevent checkboxes
    // from being toggled and links from being clicked (preventDefault)
    $("body").on("click.fromjs", function(e){
        if (!shouldHandle(e)) {return}

        e.stopPropagation();
        e.preventDefault();
        setCurrentSelectedElement(e.target)
    })

    if (!isMobile()){
        $("*").on("keydown.fromjs", function(e){
            e.preventDefault(); // prevent people typing into input fields etc
        })
        $("*").on("mouseenter.fromjs", function(e){
            if (!shouldHandle(e)) {return}
            e.stopPropagation()
            setCurrentPreviewedElement(e.target)
        })
        $("*").on("mouseleave.fromjs", function(e){
            if (!shouldHandle(e)) {return}
            setCurrentPreviewedElement(null)
        })
    }

    if (isMobile()){
        $("body").css("padding-right", "56vw")
        $("body").css("padding-left", "1vw")
        $("#fromjs-sidebar").css("width", "55vw")
        $("body").addClass("fromjsIsMobile")
    } else {
        $("body").css("padding-right", "40vw")
    }

    function setCurrentSelectedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentSelectedElement = el
            nonElementOriginSelected = false;
            inspectorPage.send("selectElement", serializeElement(el))
            updateSelectionMarker();
        })
    }

    function updateSelectionMarker(){
        if (!nonElementOriginSelected) {
            ReactDOM.render(<SelectedElementMarker el={currentSelectedElement}/>, selectedElementMarkerContainer)
        } else {
            selectedElementMarkerContainer.innerHTML = "";
        }
    }

    inspectorPage.on("UISelectParentElement", function(){
        var newSelectedEl = currentSelectedElement.parentNode;
        setCurrentSelectedElement(newSelectedEl)
    })

    inspectorPage.on("UISelectNonElementOrigin", function(){
        nonElementOriginSelected = true;
        updateSelectionMarker();
    })

    inspectorPage.on("UICloseInspector", function(){
        disableTracing();

        sidebarIframe.remove();
        container.remove();
        showShowFromJSInspectorButton(resolveFrameWorker);
        inspectorPage.close()

        $("body").css("padding-right", "0")

        enableEventListeners();
        $("body").off("click.fromjs")
        $("*").off("mouseleave.fromjs mouseenter.fromjs keydown.fromjs")

        enableTracing();
    })

    inspectorPage.on("resolveFrame", function(frameString, callback){
        resolveFrameWorker.send("resolveFrame", frameString, callback)
    })

    inspectorPage.on("getRootOriginAtChar", function(elementId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var el = getElementFromElementId(elementId)
            var initialStep = getRootOriginAtChar(el, characterIndex)
            registerOriginIdsForStep(initialStep)
            initialStep = serializeStep(initialStep)
            callback(initialStep)
        })
    })

    function serializeStep(s) {
        var originObject = s.origin;
        var isRootOrigin = false;
        originObject = originObject.serialize();
        return {
            characterIndex: s.characterIndex,
            origin: originObject
        }
    }

    // We need to a way to get an origin object by its ID
    // (which is sent by the inspector UI iframe)
    // I used to index all origins by id, but that
    // used a lot of memory
    var originsById = {}
    function getOriginById(originId){
        var origin = originsById[originId]
        if (!origin) {
            debugger;
            console.log("origin not found by id", originId)
            return
        }
        return origin;
    }
    function registerOriginIdsForStep(step){
        // current inconsistent use of originObject/origin
        var originObject = step.originObject || step.origin;

        originsById[originObject.getId()] = originObject
        originObject.inputValues.forEach(function(iv){
            if (!iv.getId) {
                // Probably an element input value... this will be filtered out by
                // .serialize later on, so it's never sent to the inspector iframe
                return iv;
            }
            originsById[iv.getId()] = iv
        })
    }

    inspectorPage.on("whereDoesCharComeFrom", function(originId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var origin = getOriginById(originId)

            whereDoesCharComeFrom([origin, characterIndex], function(steps){
                steps.forEach(registerOriginIdsForStep)
                steps = steps.map(serializeStep)
                callback(steps)
            }, resolveFrameWorker)
        })
    })

    inspectorPage.on("getCodeFilePath", function(fileName, callback){
        getCodeFilePath(fileName, callback, resolveFrameWorker)
    })

    function getElementFromElementId(elementId){
        return elementsByElementId[elementId]
    }

    function serializeElement(el) {
        if (el === null) {
            return null;
        }
        if (!el.__fromJSElementId) {
            el.__fromJSElementId = _.uniqueId()
            elementsByElementId[el.__fromJSElementId] = el
        }
        return {
            __fromJSElementId: el.__fromJSElementId,
            outerHTML: el.outerHTML,
            innerHTML: el.innerHTML,
        }
    }

    function setCurrentPreviewedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentPreviewedElement = el

            inspectorPage.send("previewElement", serializeElement(el))
            ReactDOM.render(<PreviewElementMarker el={currentPreviewedElement}/>, previewElementMarkerContainer)
        })
    }

    enableTracing();
}

export function showFromJSSidebarOnPlaygroundPage(resolveFrameWorker){
    disableTracing()

    var container = document.createElement("div")
    container.className = "fromjs-outer-container"

    var container2 = document.createElement("div")
    container2.id = "fromjs-sidebar"

    var sidebarIframe = document.createElement("iframe")
    sidebarIframe.setAttribute("style", "width: 100%; height: 100%; box-shadow: 0px 0px 20px gray;border: none")

    container2.appendChild(sidebarIframe)
    container.appendChild(container2)

    document.body.appendChild(container)
    var cssUrl = "/playground/fromjs/fromjs.css";
    var jsUrl = "/playground/fromjs/inspector.js"
    sidebarIframe.contentDocument.write(`
        <!doctype html>

Loading Inspector UI...

                <link rel="stylesheet" href="${cssUrl}">

    `)

    var elementMarkerContainer = document.createElement("div")
    container.appendChild(elementMarkerContainer)

    var previewElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(previewElementMarkerContainer)

    var selectedElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(selectedElementMarkerContainer)

    var inspectorPage = new RoundTripMessageWrapper(sidebarIframe.contentWindow, "Inspected App/Sidebar")
    // inspectorPage.beforePostMessage = disableTracing
    // inspectorPage.afterPostMessage = enableTracing

    var currentSelectedElement = null;
    var currentPreviewedElement = null;
    var nonElementOriginSelected = false;

    if (isMobile()){
        $("body").css("padding-right", "56vw")
        $("body").css("padding-left", "1vw")
        $("#fromjs-sidebar").css("width", "55vw")
        $("body").addClass("fromjsIsMobile")
    } else {
        $("body").css("padding-right", "40vw")
    }

    function setCurrentSelectedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentSelectedElement = el
            nonElementOriginSelected = false;
            inspectorPage.send("selectElement", serializeElement(el))
            updateSelectionMarker();
        })
    }

    function updateSelectionMarker(){
        if (!nonElementOriginSelected) {
            ReactDOM.render(<SelectedElementMarker el={currentSelectedElement}/>, selectedElementMarkerContainer)
        } else {
            selectedElementMarkerContainer.innerHTML = "";
        }
    }

    inspectorPage.on("UISelectParentElement", function(){
        var newSelectedEl = currentSelectedElement.parentNode;
        setCurrentSelectedElement(newSelectedEl)
    })

    inspectorPage.on("UISelectNonElementOrigin", function(){
        nonElementOriginSelected = true;
        updateSelectionMarker();
    })

    inspectorPage.on("UICloseInspector", function(){
        disableTracing();

        sidebarIframe.remove();
        container.remove();
        showShowFromJSInspectorButton(resolveFrameWorker);
        inspectorPage.close()

        $("body").css("padding-right", "0")

        enableEventListeners();
        $("body").off("click.fromjs")
        $("*").off("mouseleave.fromjs mouseenter.fromjs keydown.fromjs")

        enableTracing();
    })

    inspectorPage.on("resolveFrame", function(frameString, callback){
        resolveFrameWorker.send("resolveFrame", frameString, callback)
    })

    inspectorPage.on("InspectorReady", function(){
        setCurrentSelectedElement(document.querySelector("#result"))
    })

    inspectorPage.on("getRootOriginAtChar", function(elementId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var el = getElementFromElementId(elementId)
            var initialStep = getRootOriginAtChar(el, characterIndex)
            registerOriginIdsForStep(initialStep)
            initialStep = serializeStep(initialStep)
            callback(initialStep)
        })
    })

    function serializeStep(s) {
        var originObject = s.origin;
        var isRootOrigin = false;
        originObject = originObject.serialize();
        return {
            characterIndex: s.characterIndex,
            origin: originObject
        }
    }

    // We need to a way to get an origin object by its ID
    // (which is sent by the inspector UI iframe)
    // I used to index all origins by id, but that
    // used a lot of memory
    var originsById = {}
    function getOriginById(originId){
        var origin = originsById[originId]
        if (!origin) {
            debugger;
            console.log("origin not found by id", originId)
            return
        }
        return origin;
    }
    function registerOriginIdsForStep(step){
        // current inconsistent use of originObject/origin
        var originObject = step.originObject || step.origin;

        originsById[originObject.getId()] = originObject
        originObject.inputValues.forEach(function(iv){
            if (!iv.getId) {
                // Probably an element input value... this will be filtered out by
                // .serialize later on, so it's never sent to the inspector iframe
                return iv;
            }
            originsById[iv.getId()] = iv
        })
    }

    inspectorPage.on("whereDoesCharComeFrom", function(originId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var origin = getOriginById(originId)

            whereDoesCharComeFrom([origin, characterIndex], function(steps){
                steps.forEach(registerOriginIdsForStep)
                steps = steps.map(serializeStep)
                callback(steps)
            }, resolveFrameWorker)
        })
    })

    inspectorPage.on("getCodeFilePath", function(fileName, callback){
        getCodeFilePath(fileName, callback, resolveFrameWorker)
    })

    function getElementFromElementId(elementId){
        return elementsByElementId[elementId]
    }

    function serializeElement(el) {
        if (el === null) {
            return null;
        }
        if (!el.__fromJSElementId) {
            el.__fromJSElementId = _.uniqueId()
            elementsByElementId[el.__fromJSElementId] = el
        }
        return {
            __fromJSElementId: el.__fromJSElementId,
            outerHTML: el.outerHTML,
            innerHTML: el.innerHTML,
        }
    }

    function setCurrentPreviewedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentPreviewedElement = el

            inspectorPage.send("previewElement", serializeElement(el))
            ReactDOM.render(<PreviewElementMarker el={currentPreviewedElement}/>, previewElementMarkerContainer)
        })
    }
}

export function showShowFromJSInspectorButton(resolveFrameWorker){
    var btn = $("<button>")
    btn.text("Show FromJS Inspector")
    btn.click(function(e){
        btn.remove()
        showFromJSSidebar(resolveFrameWorker)
        e.stopPropagation();
    })
    btn.addClass("fromjs-show-inspector-button toggle-inspector-button")
    $("body").append(btn)
}
<commit_msg>Add fromJSInspectElement function to be used from console.<commit_after>import $ from "jquery"
import React from "react"
import ReactDOM from "react-dom"
import isMobile from "../isMobile"
import _ from "underscore"
import whereDoesCharComeFrom from "../whereDoesCharComeFrom"
import getRootOriginAtChar from "../getRootOriginAtChar"
import { OriginPath, PreviewElementMarker, SelectedElementMarker } from "../ui/ui"
import {disableTracing, enableTracing, disableEventListeners, enableEventListeners,runFunctionWithTracingDisabled} from "../tracing/tracing"
import RoundTripMessageWrapper from "../RoundTripMessageWrapper"
import getCodeFilePath from "./getCodeFilePath"

var elementsByElementId = {}

export default function showFromJSSidebar(resolveFrameWorker){
    disableTracing()

    var container = document.createElement("div")
    container.className = "fromjs-outer-container"

    var container2 = document.createElement("div")
    container2.id = "fromjs-sidebar"

    var sidebarIframe = document.createElement("iframe")
    sidebarIframe.setAttribute("style", "width: 100%; height: 100%; box-shadow: 0px 0px 20px gray;border: none")

    container2.appendChild(sidebarIframe)
    container.appendChild(container2)

    document.body.appendChild(container)
    var rootUrl = "/fromjs-internals/"
    if (window.fromJSInternalsRoot){
        rootUrl = window.fromJSInternalsRoot
    }
    var cssUrl = rootUrl + "fromjs.css";
    var jsUrl = rootUrl + "inspector.js";
    sidebarIframe.contentDocument.write(`
        <!doctype html>

Loading Inspector UI...

                <link rel="stylesheet" href="${cssUrl}">

    `)

    var elementMarkerContainer = document.createElement("div")
    container.appendChild(elementMarkerContainer)

    var previewElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(previewElementMarkerContainer)

    var selectedElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(selectedElementMarkerContainer)

    function shouldHandle(e) {
        if ($(e.target).closest("#fromjs-sidebar").length !== 0) {
            return false
        }
        if ($(e.target).is("html, body")) {
            return false
        }
        return true
    }

    disableEventListeners()

    var inspectorPage = new RoundTripMessageWrapper(sidebarIframe.contentWindow, "Inspected App/Sidebar")
    inspectorPage.beforePostMessage = disableTracing
    inspectorPage.afterPostMessage = enableTracing

    var currentSelectedElement = null;
    var currentPreviewedElement = null;
    var nonElementOriginSelected = false;
    // We already disable event listeners created with addEventListener
    // but not e.g. onclick attributes, or prevent checkboxes
    // from being toggled and links from being clicked (preventDefault)
    $("body").on("click.fromjs", function(e){
        if (!shouldHandle(e)) {return}

        e.stopPropagation();
        e.preventDefault();
        setCurrentSelectedElement(e.target)
    })

    if (!isMobile()){
        $("*").on("keydown.fromjs", function(e){
            e.preventDefault(); // prevent people typing into input fields etc
        })
        $("*").on("mouseenter.fromjs", function(e){
            if (!shouldHandle(e)) {return}
            e.stopPropagation()
            setCurrentPreviewedElement(e.target)
        })
        $("*").on("mouseleave.fromjs", function(e){
            if (!shouldHandle(e)) {return}
            setCurrentPreviewedElement(null)
        })
    }

    if (isMobile()){
        $("body").css("padding-right", "56vw")
        $("body").css("padding-left", "1vw")
        $("#fromjs-sidebar").css("width", "55vw")
        $("body").addClass("fromjsIsMobile")
    } else {
        $("body").css("padding-right", "40vw")
    }

    // this should be part of public API, to make it easier to inspect elements
    // that are hard to select in the UI
    window.fromJSInspectElememt = setCurrentSelectedElement

    function setCurrentSelectedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentSelectedElement = el
            nonElementOriginSelected = false;
            inspectorPage.send("selectElement", serializeElement(el))
            updateSelectionMarker();
        })
    }

    function updateSelectionMarker(){
        if (!nonElementOriginSelected) {
            ReactDOM.render(<SelectedElementMarker el={currentSelectedElement}/>, selectedElementMarkerContainer)
        } else {
            selectedElementMarkerContainer.innerHTML = "";
        }
    }

    inspectorPage.on("UISelectParentElement", function(){
        var newSelectedEl = currentSelectedElement.parentNode;
        setCurrentSelectedElement(newSelectedEl)
    })

    inspectorPage.on("UISelectNonElementOrigin", function(){
        nonElementOriginSelected = true;
        updateSelectionMarker();
    })

    inspectorPage.on("UICloseInspector", function(){
        delete window.fromJSInspectElememt

        disableTracing();

        sidebarIframe.remove();
        container.remove();
        showShowFromJSInspectorButton(resolveFrameWorker);
        inspectorPage.close()

        $("body").css("padding-right", "0")

        enableEventListeners();
        $("body").off("click.fromjs")
        $("*").off("mouseleave.fromjs mouseenter.fromjs keydown.fromjs")

        enableTracing();
    })

    inspectorPage.on("resolveFrame", function(frameString, callback){
        resolveFrameWorker.send("resolveFrame", frameString, callback)
    })

    inspectorPage.on("getRootOriginAtChar", function(elementId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var el = getElementFromElementId(elementId)
            var initialStep = getRootOriginAtChar(el, characterIndex)
            registerOriginIdsForStep(initialStep)
            initialStep = serializeStep(initialStep)
            callback(initialStep)
        })
    })

    function serializeStep(s) {
        var originObject = s.origin;
        var isRootOrigin = false;
        originObject = originObject.serialize();
        return {
            characterIndex: s.characterIndex,
            origin: originObject
        }
    }

    // We need to a way to get an origin object by its ID
    // (which is sent by the inspector UI iframe)
    // I used to index all origins by id, but that
    // used a lot of memory
    var originsById = {}
    function getOriginById(originId){
        var origin = originsById[originId]
        if (!origin) {
            debugger;
            console.log("origin not found by id", originId)
            return
        }
        return origin;
    }
    function registerOriginIdsForStep(step){
        // current inconsistent use of originObject/origin
        var originObject = step.originObject || step.origin;

        originsById[originObject.getId()] = originObject
        originObject.inputValues.forEach(function(iv){
            if (!iv.getId) {
                // Probably an element input value... this will be filtered out by
                // .serialize later on, so it's never sent to the inspector iframe
                return iv;
            }
            originsById[iv.getId()] = iv
        })
    }

    inspectorPage.on("whereDoesCharComeFrom", function(originId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var origin = getOriginById(originId)

            whereDoesCharComeFrom([origin, characterIndex], function(steps){
                steps.forEach(registerOriginIdsForStep)
                steps = steps.map(serializeStep)
                callback(steps)
            }, resolveFrameWorker)
        })
    })

    inspectorPage.on("getCodeFilePath", function(fileName, callback){
        getCodeFilePath(fileName, callback, resolveFrameWorker)
    })

    function getElementFromElementId(elementId){
        return elementsByElementId[elementId]
    }

    function serializeElement(el) {
        if (el === null) {
            return null;
        }
        if (!el.__fromJSElementId) {
            el.__fromJSElementId = _.uniqueId()
            elementsByElementId[el.__fromJSElementId] = el
        }
        return {
            __fromJSElementId: el.__fromJSElementId,
            outerHTML: el.outerHTML,
            innerHTML: el.innerHTML,
        }
    }

    function setCurrentPreviewedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentPreviewedElement = el

            inspectorPage.send("previewElement", serializeElement(el))
            ReactDOM.render(<PreviewElementMarker el={currentPreviewedElement}/>, previewElementMarkerContainer)
        })
    }

    enableTracing();
}

export function showFromJSSidebarOnPlaygroundPage(resolveFrameWorker){
    disableTracing()

    var container = document.createElement("div")
    container.className = "fromjs-outer-container"

    var container2 = document.createElement("div")
    container2.id = "fromjs-sidebar"

    var sidebarIframe = document.createElement("iframe")
    sidebarIframe.setAttribute("style", "width: 100%; height: 100%; box-shadow: 0px 0px 20px gray;border: none")

    container2.appendChild(sidebarIframe)
    container.appendChild(container2)

    document.body.appendChild(container)
    var cssUrl = "/playground/fromjs/fromjs.css";
    var jsUrl = "/playground/fromjs/inspector.js"
    sidebarIframe.contentDocument.write(`
        <!doctype html>

Loading Inspector UI...

                <link rel="stylesheet" href="${cssUrl}">

    `)

    var elementMarkerContainer = document.createElement("div")
    container.appendChild(elementMarkerContainer)

    var previewElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(previewElementMarkerContainer)

    var selectedElementMarkerContainer = document.createElement("div")
    elementMarkerContainer.appendChild(selectedElementMarkerContainer)

    var inspectorPage = new RoundTripMessageWrapper(sidebarIframe.contentWindow, "Inspected App/Sidebar")
    // inspectorPage.beforePostMessage = disableTracing
    // inspectorPage.afterPostMessage = enableTracing

    var currentSelectedElement = null;
    var currentPreviewedElement = null;
    var nonElementOriginSelected = false;

    if (isMobile()){
        $("body").css("padding-right", "56vw")
        $("body").css("padding-left", "1vw")
        $("#fromjs-sidebar").css("width", "55vw")
        $("body").addClass("fromjsIsMobile")
    } else {
        $("body").css("padding-right", "40vw")
    }

    function setCurrentSelectedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentSelectedElement = el
            nonElementOriginSelected = false;
            inspectorPage.send("selectElement", serializeElement(el))
            updateSelectionMarker();
        })
    }

    function updateSelectionMarker(){
        if (!nonElementOriginSelected) {
            ReactDOM.render(<SelectedElementMarker el={currentSelectedElement}/>, selectedElementMarkerContainer)
        } else {
            selectedElementMarkerContainer.innerHTML = "";
        }
    }

    inspectorPage.on("UISelectParentElement", function(){
        var newSelectedEl = currentSelectedElement.parentNode;
        setCurrentSelectedElement(newSelectedEl)
    })

    inspectorPage.on("UISelectNonElementOrigin", function(){
        nonElementOriginSelected = true;
        updateSelectionMarker();
    })

    inspectorPage.on("UICloseInspector", function(){
        disableTracing();

        sidebarIframe.remove();
        container.remove();
        showShowFromJSInspectorButton(resolveFrameWorker);
        inspectorPage.close()

        $("body").css("padding-right", "0")

        enableEventListeners();
        $("body").off("click.fromjs")
        $("*").off("mouseleave.fromjs mouseenter.fromjs keydown.fromjs")

        enableTracing();
    })

    inspectorPage.on("resolveFrame", function(frameString, callback){
        resolveFrameWorker.send("resolveFrame", frameString, callback)
    })

    inspectorPage.on("InspectorReady", function(){
        setCurrentSelectedElement(document.querySelector("#result"))
    })

    inspectorPage.on("getRootOriginAtChar", function(elementId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var el = getElementFromElementId(elementId)
            var initialStep = getRootOriginAtChar(el, characterIndex)
            registerOriginIdsForStep(initialStep)
            initialStep = serializeStep(initialStep)
            callback(initialStep)
        })
    })

    function serializeStep(s) {
        var originObject = s.origin;
        var isRootOrigin = false;
        originObject = originObject.serialize();
        return {
            characterIndex: s.characterIndex,
            origin: originObject
        }
    }

    // We need to a way to get an origin object by its ID
    // (which is sent by the inspector UI iframe)
    // I used to index all origins by id, but that
    // used a lot of memory
    var originsById = {}
    function getOriginById(originId){
        var origin = originsById[originId]
        if (!origin) {
            debugger;
            console.log("origin not found by id", originId)
            return
        }
        return origin;
    }
    function registerOriginIdsForStep(step){
        // current inconsistent use of originObject/origin
        var originObject = step.originObject || step.origin;

        originsById[originObject.getId()] = originObject
        originObject.inputValues.forEach(function(iv){
            if (!iv.getId) {
                // Probably an element input value... this will be filtered out by
                // .serialize later on, so it's never sent to the inspector iframe
                return iv;
            }
            originsById[iv.getId()] = iv
        })
    }

    inspectorPage.on("whereDoesCharComeFrom", function(originId, characterIndex, callback){
        runFunctionWithTracingDisabled(function(){
            var origin = getOriginById(originId)

            whereDoesCharComeFrom([origin, characterIndex], function(steps){
                steps.forEach(registerOriginIdsForStep)
                steps = steps.map(serializeStep)
                callback(steps)
            }, resolveFrameWorker)
        })
    })

    inspectorPage.on("getCodeFilePath", function(fileName, callback){
        getCodeFilePath(fileName, callback, resolveFrameWorker)
    })

    function getElementFromElementId(elementId){
        return elementsByElementId[elementId]
    }

    function serializeElement(el) {
        if (el === null) {
            return null;
        }
        if (!el.__fromJSElementId) {
            el.__fromJSElementId = _.uniqueId()
            elementsByElementId[el.__fromJSElementId] = el
        }
        return {
            __fromJSElementId: el.__fromJSElementId,
            outerHTML: el.outerHTML,
            innerHTML: el.innerHTML,
        }
    }

    function setCurrentPreviewedElement(el){
        runFunctionWithTracingDisabled(function(){
            currentPreviewedElement = el

            inspectorPage.send("previewElement", serializeElement(el))
            ReactDOM.render(<PreviewElementMarker el={currentPreviewedElement}/>, previewElementMarkerContainer)
        })
    }
}

export function showShowFromJSInspectorButton(resolveFrameWorker){
    var btn = $("<button>")
    btn.text("Show FromJS Inspector")
    btn.click(function(e){
        btn.remove()
        showFromJSSidebar(resolveFrameWorker)
        e.stopPropagation();
    })
    btn.addClass("fromjs-show-inspector-button toggle-inspector-button")
    $("body").append(btn)
}
<|endoftext|>


<commit_before>/**
 * The Thingy class is available to all.
 * @public
 * @class
 */
function Ukulele() {
	"use strict";
	var controllersDefinition = {};
	var viewControllerArray = [];

	var copyControllers = {};
	var self = this;
	var watchTimer;
	this.refreshHandler = undefined;
	this.parentUku = undefined;
	this.init = function() {
			$(document).ready(function() {
				manageApplication();
				watchBoundAttribute();
			});
	};
	this.registerController = function(instanceName, controllerInst) {
			viewControllerArray.push({
				"view" : $(this),
				"controller" : controllerInst
			});
			var controllerModel = new ControllerModel(controllerInst);
			controllersDefinition[instanceName] = controllerModel;
	};
	this.dealWithElement = function($element, watch) {
			analyizeElement($element);
			if (watch) {
				watchBoundAttribute();
			}
	};
	this.loadIncludeElement = function($element) {
			if($element.attr("load") === "false"){
				$element.attr("load",true);
				analyizeElement($element.parent());
			}
	};
	this.getControllerModelByName = function(expression) {
		return getBoundControllerModelByName(expression);
	};

	//心跳功能来判断bound的attribute有没有在内存中被更新，从而主动刷新视图
	function watchBoundAttribute() {
		for (var alias in controllersDefinition) {
			var controllerModel = controllersDefinition[alias];
			var controller = controllerModel.controllerInstance;
			var previousCtrlModel = copyControllers[alias];
			for (var i = 0; i < controllerModel.boundAttrs.length; i++) {
				var boundAttr = controllerModel.boundAttrs[i];
				var attrName = boundAttr.attributeName;
				if (previousCtrlModel) {
					var finalValue = UkuleleUtil.getFinalValue(controller, attrName);
					var previousFinalValue = UkuleleUtil.getFinalValue(previousCtrlModel, attrName);
					if (!ObjectUtil.compare(previousFinalValue, finalValue)) {
						var changedBoundAttrs = controllerModel.getBoundAttrByName(attrName);
						for (var j = 0; j < changedBoundAttrs.length; j++) {
							var changedBoundAttr = changedBoundAttrs[j];
							if (changedBoundAttr.ukuTag === "repeat") {
								//1.repeat的处理，先把repeat的render逻辑写在这里，以后移到各自的class
								changedBoundAttr.renderRepeat(controller);
							} else if (changedBoundAttr.expression !== null) {
								//2. 处理expression
								changedBoundAttr.renderExpression(controller);
							} else {
								//3. 与属性attribute bind，目前理论上全属性支持
								changedBoundAttr.renderAttribute(controller);
							}
							if (self.refreshHandler) {
								self.refreshHandler.call(null);
							}
						}
					}
				}

			}
			previousCtrlModel = ObjectUtil.deepClone(controller);
			delete copyControllers[alias];
			copyControllers[alias] = previousCtrlModel;
		}
		watchTimer = setTimeout(watchBoundAttribute, 500);
	}
	//解析html中各个uku的tag
	function analyizeElement($element) {
		searchIncludeTag($element,function(){
			var subElements = [];
			//scan element which has uku-* tag
			var isSelfHasUkuTag = $element.fuzzyFind('uku-');
			if (isSelfHasUkuTag) {
				subElements.push(isSelfHasUkuTag);
			}
			$element.find("*").each(function() {
				var matchElement = $(this).fuzzyFind('uku-');
				if (matchElement && !UkuleleUtil.isInRepeat($(matchElement))) {
					subElements.push(matchElement);
				}
			});
			searchExpression($element);
			//解析绑定 attribute，注册event
			for (var i = 0; i < subElements.length; i++) {
				var subElement = subElements[i];
				for (var j = 0; j < subElement.attributes.length; j++) {
					var attribute = subElement.attributes[j];
					if (UkuleleUtil.searchUkuAttrTag(attribute.nodeName) > -1) {
						var attrName = attribute.nodeName.split('-')[1];
						if (attrName !== "application") {
							if (attrName.search('on') === 0) {
								//is an event
								if (!UkuleleUtil.isRepeat($(subElement)) && !UkuleleUtil.isInRepeat($(subElement))) {
									dealWithEvent($(subElement), attrName);
								}
							} else if (attrName.search('repeat') !== -1) {
								//is an repeat
								dealWithRepeat($(subElement));
							} else {
								//is an attribute
								if (!UkuleleUtil.isRepeat($(subElement)) && !UkuleleUtil.isInRepeat($(subElement))) {
									dealWithAttribute($(subElement), attrName);
								}
							}
						}
					}
				}
			}
			if (self.refreshHandler) {
				self.refreshHandler.call(null);
			}
		});

		function searchIncludeTag($element,retFunc){
			var tags = $element.find('.uku-include');
			var index = 0;
			if(index < tags.length){
				dealWithInclude(index);
			}else{
				retFunc();
			}
			function dealWithInclude(index){
				var $tag = $(tags[index]);
				var isLoad = $tag.attr("load");
				if(isLoad === "false"){
					index++;
					if(index < tags.length){
						dealWithInclude(index);
					}else{
						retFunc();
					}
				}else{
					var src = $tag.attr("src");
					$tag.load(src,function(){
						searchIncludeTag($tag,function(){
							index++;
							if(index < tags.length){
								dealWithInclude(index);
							}else{
								retFunc();
							}
						});
					});
				}
			}
		}

		//scan element which has expression {{}}
		function searchExpression($element) {
			if (UkuleleUtil.searchUkuExpTag($element.directText()) !== -1) {
				if (!UkuleleUtil.isRepeat($element) && !UkuleleUtil.isInRepeat($element)) {
					//normal expression
					dealWithExpression($element);
				}
			}
			$element.children().each(function() {
				searchExpression($(this));
			});
		}
		//处理绑定的expression
		function dealWithExpression(element) {
			var expression = element.directText();
			if (UkuleleUtil.searchUkuExpTag(expression) !== -1) {
				var attr = expression.slice(2, -2);
				var result = getBoundAttributeValue(attr);
				element.directText(result);
				var boundAttr = new BoundAttribute(attr, null, expression, element);
				var controllerModel = getBoundControllerModelByName(attr);
				controllerModel.addBoundAttr(boundAttr);
			}
		}
		//处理绑定的attribute
		function dealWithAttribute(element, tagName) {
			var attr = element.attr("uku-" + tagName);
			var result = getBoundAttributeValue(attr);
			element.attr(tagName, result);
			var boundAttr = new BoundAttribute(attr, tagName, null, element);
			var controllerModel = getBoundControllerModelByName(attr);
			controllerModel.addBoundAttr(boundAttr);
			var elementName = element[0].tagName;
			if ((elementName === "INPUT" || elementName === "SELECT" || elementName === "TEXTAREA") && tagName === "value") {
				element.change(function(e) {
					var _attr = UkuleleUtil.getFinalAttribute(attr);
					var temp = _attr.split(".");
					var finalInstance = controllerModel.controllerInstance;
					for (var i = 0; i < temp.length - 1; i++) {
						finalInstance = finalInstance[temp[i]];
					}
					finalInstance[temp[temp.length - 1]] = element.val();
				});
			}
		}
		//处理 事件 event
		function dealWithEvent(element, eventName) {
			var expression = element.attr("uku-" + eventName);
			var eventNameInJQuery = eventName.substring(2);
			element.bind(eventNameInJQuery, function() {
				getBoundAttributeValue(expression,arguments);
			});
		}
		//处理 repeat
		function dealWithRepeat(element) {
			var repeatExpression = element.attr("uku-repeat");
			var tempArr = repeatExpression.split(' in ');
			var itemName = tempArr[0];
			var attr = tempArr[1];
			var controllerModel = getBoundControllerModelByName(attr);
			var controllerInst = controllerModel.controllerInstance;
			var boundAttr = new BoundAttribute(attr, "repeat", itemName, element, self);
			controllerModel.addBoundAttr(boundAttr);
			boundAttr.renderRepeat(controllerInst);
		}
	}
	function getBoundControllerModelByName(attrName) {
		var instanceName = UkuleleUtil.getBoundModelInstantName(attrName);
		var controllerModel = controllersDefinition[instanceName];
		if (!controllerModel) {
			var tempArr = attrName.split(".");
			var isParentScope = tempArr[0];
			if (isParentScope === "parent" && self.parentUku) {
				tempArr.shift();
				attrName = tempArr.join(".");
				return self.parentUku.getControllerModelByName(attrName);
			}
		}
		return controllerModel;
	}
	function getBoundAttributeValue(attr,argu) {
		var controllerModel = getBoundControllerModelByName(attr);
		var controllerInst = controllerModel.controllerInstance;
		var index = UkuleleUtil.searchUkuFuncArg(attr);
		var result;
		if (index === -1) {
			//is a attribute
			result = UkuleleUtil.getFinalValue(controllerInst, attr);
		} else {
			//is a function
			var functionName = attr.substring(0, index);
			var finalValueObject = UkuleleUtil.getAttributeFinalValueAndParent(controllerInst, functionName);
			var finalValue = finalValueObject.value;
			if(finalValue === undefined){
	        	return finalValue;
	        }
			var _arguments = attr.substring(index + 1, attr.length - 1);
			var withoutArgument = false;
			if (_arguments === "") {
				withoutArgument = true;
			}
			_arguments = _arguments.split(",");
			if (!withoutArgument) {
				var new_arguments = [];
				for (var i = 0; i < _arguments.length; i++) {
					var argument = _arguments[i];
					var agrumentInst = getBoundControllerModelByName(argument).controllerInstance;
					var temp;
					if (argument.split(".").length === 1) {
						temp = agrumentInst;
					} else {
						temp = UkuleleUtil.getFinalValue(agrumentInst, argument);
					}
					new_arguments.push(temp);
				}
				result = finalValue.apply(finalValueObject.parent, new_arguments.concat(argu));
			} else {
				result = finalValue.apply(finalValueObject.parent, argu);
			}
		}
		return result;
	}

	function manageApplication() {
		$("[uku-application]").each(function() {
			analyizeElement($(this));
		});
	}
}<commit_msg>add document comment to ukulele.js<commit_after>/**
 * Create a new Ukulele
 * @class
 */
function Ukulele() {
	"use strict";
	var controllersDefinition = {};
	var viewControllerArray = [];
	var copyControllers = {};
	var self = this;
	var watchTimer;
	/*
	 * @access a callback function when view was refreshed.
	 */
	this.refreshHandler = undefined;
	/*
	 * @access When using uku-repeat, parentUku to reference the Parent controller model's uku
	 */
	this.parentUku = undefined;
	this.init = function() {
			$(document).ready(function() {
				manageApplication();
				watchBoundAttribute();
			});
	};
	/*
	 * @description Register a controller model which you want to bind with view
	 * @param {string} instanceName controller's alias
	 * @param {object}  controllerInst controller's instance
	 */
	this.registerController = function(instanceName, controllerInst) {
			viewControllerArray.push({
				"view" : $(this),
				"controller" : controllerInst
			});
			var controllerModel = new ControllerModel(controllerInst);
			controllersDefinition[instanceName] = controllerModel;
	};
	/*
	 * @description deal with partial html element you want to manage by UkuleleJS
	 * @param {object} $element jquery html object e.g. $("#myButton")
	 * @param {boolean} watch whether refresh automatically or not
	 */
	this.dealWithElement = function($element, watch) {
			analyizeElement($element);
			if (watch) {
				watchBoundAttribute();
			}
	};
	/*
	 * @description deal with the uku-include componnent which need be to lazy loaded.
	 * @param {object} $element jquery html object e.g. $("#myButton")
	 */
	this.loadIncludeElement = function($element) {
			if($element.attr("load") === "false"){
				$element.attr("load",true);
				analyizeElement($element.parent());
			}
	};
	/*
	 * @description get the controller model's instance by alias.
	 * @param {object} expression  controller model's alias.
	 * @returns {object} controller model's instance
	 */
	this.getControllerModelByName = function(expression) {
		return getBoundControllerModelByName(expression);
	};

	//心跳功能来判断bound的attribute有没有在内存中被更新，从而主动刷新视图
	function watchBoundAttribute() {
		for (var alias in controllersDefinition) {
			var controllerModel = controllersDefinition[alias];
			var controller = controllerModel.controllerInstance;
			var previousCtrlModel = copyControllers[alias];
			for (var i = 0; i < controllerModel.boundAttrs.length; i++) {
				var boundAttr = controllerModel.boundAttrs[i];
				var attrName = boundAttr.attributeName;
				if (previousCtrlModel) {
					var finalValue = UkuleleUtil.getFinalValue(controller, attrName);
					var previousFinalValue = UkuleleUtil.getFinalValue(previousCtrlModel, attrName);
					if (!ObjectUtil.compare(previousFinalValue, finalValue)) {
						var changedBoundAttrs = controllerModel.getBoundAttrByName(attrName);
						for (var j = 0; j < changedBoundAttrs.length; j++) {
							var changedBoundAttr = changedBoundAttrs[j];
							if (changedBoundAttr.ukuTag === "repeat") {
								//1.repeat的处理，先把repeat的render逻辑写在这里，以后移到各自的class
								changedBoundAttr.renderRepeat(controller);
							} else if (changedBoundAttr.expression !== null) {
								//2. 处理expression
								changedBoundAttr.renderExpression(controller);
							} else {
								//3. 与属性attribute bind，目前理论上全属性支持
								changedBoundAttr.renderAttribute(controller);
							}
							if (self.refreshHandler) {
								self.refreshHandler.call(null);
							}
						}
					}
				}

			}
			previousCtrlModel = ObjectUtil.deepClone(controller);
			delete copyControllers[alias];
			copyControllers[alias] = previousCtrlModel;
		}
		watchTimer = setTimeout(watchBoundAttribute, 500);
	}
	//解析html中各个uku的tag
	function analyizeElement($element) {
		searchIncludeTag($element,function(){
			var subElements = [];
			//scan element which has uku-* tag
			var isSelfHasUkuTag = $element.fuzzyFind('uku-');
			if (isSelfHasUkuTag) {
				subElements.push(isSelfHasUkuTag);
			}
			$element.find("*").each(function() {
				var matchElement = $(this).fuzzyFind('uku-');
				if (matchElement && !UkuleleUtil.isInRepeat($(matchElement))) {
					subElements.push(matchElement);
				}
			});
			searchExpression($element);
			//解析绑定 attribute，注册event
			for (var i = 0; i < subElements.length; i++) {
				var subElement = subElements[i];
				for (var j = 0; j < subElement.attributes.length; j++) {
					var attribute = subElement.attributes[j];
					if (UkuleleUtil.searchUkuAttrTag(attribute.nodeName) > -1) {
						var attrName = attribute.nodeName.split('-')[1];
						if (attrName !== "application") {
							if (attrName.search('on') === 0) {
								//is an event
								if (!UkuleleUtil.isRepeat($(subElement)) && !UkuleleUtil.isInRepeat($(subElement))) {
									dealWithEvent($(subElement), attrName);
								}
							} else if (attrName.search('repeat') !== -1) {
								//is an repeat
								dealWithRepeat($(subElement));
							} else {
								//is an attribute
								if (!UkuleleUtil.isRepeat($(subElement)) && !UkuleleUtil.isInRepeat($(subElement))) {
									dealWithAttribute($(subElement), attrName);
								}
							}
						}
					}
				}
			}
			if (self.refreshHandler) {
				self.refreshHandler.call(null);
			}
		});

		function searchIncludeTag($element,retFunc){
			var tags = $element.find('.uku-include');
			var index = 0;
			if(index < tags.length){
				dealWithInclude(index);
			}else{
				retFunc();
			}
			function dealWithInclude(index){
				var $tag = $(tags[index]);
				var isLoad = $tag.attr("load");
				if(isLoad === "false"){
					index++;
					if(index < tags.length){
						dealWithInclude(index);
					}else{
						retFunc();
					}
				}else{
					var src = $tag.attr("src");
					$tag.load(src,function(){
						searchIncludeTag($tag,function(){
							index++;
							if(index < tags.length){
								dealWithInclude(index);
							}else{
								retFunc();
							}
						});
					});
				}
			}
		}

		//scan element which has expression {{}}
		function searchExpression($element) {
			if (UkuleleUtil.searchUkuExpTag($element.directText()) !== -1) {
				if (!UkuleleUtil.isRepeat($element) && !UkuleleUtil.isInRepeat($element)) {
					//normal expression
					dealWithExpression($element);
				}
			}
			$element.children().each(function() {
				searchExpression($(this));
			});
		}
		//处理绑定的expression
		function dealWithExpression(element) {
			var expression = element.directText();
			if (UkuleleUtil.searchUkuExpTag(expression) !== -1) {
				var attr = expression.slice(2, -2);
				var result = getBoundAttributeValue(attr);
				element.directText(result);
				var boundAttr = new BoundAttribute(attr, null, expression, element);
				var controllerModel = getBoundControllerModelByName(attr);
				controllerModel.addBoundAttr(boundAttr);
			}
		}
		//处理绑定的attribute
		function dealWithAttribute(element, tagName) {
			var attr = element.attr("uku-" + tagName);
			var result = getBoundAttributeValue(attr);
			element.attr(tagName, result);
			var boundAttr = new BoundAttribute(attr, tagName, null, element);
			var controllerModel = getBoundControllerModelByName(attr);
			controllerModel.addBoundAttr(boundAttr);
			var elementName = element[0].tagName;
			if ((elementName === "INPUT" || elementName === "SELECT" || elementName === "TEXTAREA") && tagName === "value") {
				element.change(function(e) {
					var _attr = UkuleleUtil.getFinalAttribute(attr);
					var temp = _attr.split(".");
					var finalInstance = controllerModel.controllerInstance;
					for (var i = 0; i < temp.length - 1; i++) {
						finalInstance = finalInstance[temp[i]];
					}
					finalInstance[temp[temp.length - 1]] = element.val();
				});
			}
		}
		//处理 事件 event
		function dealWithEvent(element, eventName) {
			var expression = element.attr("uku-" + eventName);
			var eventNameInJQuery = eventName.substring(2);
			element.bind(eventNameInJQuery, function() {
				getBoundAttributeValue(expression,arguments);
			});
		}
		//处理 repeat
		function dealWithRepeat(element) {
			var repeatExpression = element.attr("uku-repeat");
			var tempArr = repeatExpression.split(' in ');
			var itemName = tempArr[0];
			var attr = tempArr[1];
			var controllerModel = getBoundControllerModelByName(attr);
			var controllerInst = controllerModel.controllerInstance;
			var boundAttr = new BoundAttribute(attr, "repeat", itemName, element, self);
			controllerModel.addBoundAttr(boundAttr);
			boundAttr.renderRepeat(controllerInst);
		}
	}
	function getBoundControllerModelByName(attrName) {
		var instanceName = UkuleleUtil.getBoundModelInstantName(attrName);
		var controllerModel = controllersDefinition[instanceName];
		if (!controllerModel) {
			var tempArr = attrName.split(".");
			var isParentScope = tempArr[0];
			if (isParentScope === "parent" && self.parentUku) {
				tempArr.shift();
				attrName = tempArr.join(".");
				return self.parentUku.getControllerModelByName(attrName);
			}
		}
		return controllerModel;
	}
	function getBoundAttributeValue(attr,argu) {
		var controllerModel = getBoundControllerModelByName(attr);
		var controllerInst = controllerModel.controllerInstance;
		var index = UkuleleUtil.searchUkuFuncArg(attr);
		var result;
		if (index === -1) {
			//is a attribute
			result = UkuleleUtil.getFinalValue(controllerInst, attr);
		} else {
			//is a function
			var functionName = attr.substring(0, index);
			var finalValueObject = UkuleleUtil.getAttributeFinalValueAndParent(controllerInst, functionName);
			var finalValue = finalValueObject.value;
			if(finalValue === undefined){
	        	return finalValue;
	        }
			var _arguments = attr.substring(index + 1, attr.length - 1);
			var withoutArgument = false;
			if (_arguments === "") {
				withoutArgument = true;
			}
			_arguments = _arguments.split(",");
			if (!withoutArgument) {
				var new_arguments = [];
				for (var i = 0; i < _arguments.length; i++) {
					var argument = _arguments[i];
					var agrumentInst = getBoundControllerModelByName(argument).controllerInstance;
					var temp;
					if (argument.split(".").length === 1) {
						temp = agrumentInst;
					} else {
						temp = UkuleleUtil.getFinalValue(agrumentInst, argument);
					}
					new_arguments.push(temp);
				}
				result = finalValue.apply(finalValueObject.parent, new_arguments.concat(argu));
			} else {
				result = finalValue.apply(finalValueObject.parent, argu);
			}
		}
		return result;
	}

	function manageApplication() {
		$("[uku-application]").each(function() {
			analyizeElement($(this));
		});
	}
}<|endoftext|>


<commit_before>// Synchronizer.js - in api/services

"use strict";

module.exports = {
    synchronizeScopus
};

async function synchronizeScopus() {
    const documentsToSynchronize = await Document.find({
        editedAfterImport: false,
        kind: [DocumentKinds.DRAFT, DocumentKinds.VERIFIED],
        origin: DocumentOrigins.SCOPUS
    });
    for (let doc of documentsToSynchronize) {
        const externalDoc = await Document.findOne({
            scopusId: doc.scopusId,
            kind: DocumentKinds.EXTERNAL,
            origin: DocumentOrigins.SCOPUS
        });
        const externalDocData = Document.selectData(externalDoc);
        delete externalDocData.kind;
        const d = await Document.update(doc.id, externalDocData);
    }
}<commit_msg>Added log in the synchronization service.<commit_after>// Synchronizer.js - in api/services

"use strict";

module.exports = {
    synchronizeScopus
};

async function synchronizeScopus() {
    sails.log.info("Scopus synchronization starting");
    const documentsToSynchronize = await Document.find({
        editedAfterImport: false,
        kind: [DocumentKinds.DRAFT, DocumentKinds.VERIFIED],
        origin: DocumentOrigins.SCOPUS
    });
    for (let doc of documentsToSynchronize) {
        const externalDoc = await Document.findOne({
            scopusId: doc.scopusId,
            kind: DocumentKinds.EXTERNAL,
            origin: DocumentOrigins.SCOPUS
        });
        const externalDocData = Document.selectData(externalDoc);
        delete externalDocData.kind;
        const d = await Document.update(doc.id, externalDocData);
    }
    sails.log.info(documentsToSynchronize.length + " documents synchronized");
}<|endoftext|>


<commit_before>const blockedURLs = ['indeed.co.uk'];

export const isBlockedURLs = (url = '') => {
	return !!blockedURLs.find((u) => url.includes(u));
};
<commit_msg>generalize blocked url<commit_after>const blockedURLs = ['indeed.co'];

export const isBlockedURLs = (url = '') => {
	return !!blockedURLs.find((u) => url.includes(u));
};
<|endoftext|>


<commit_before>var lib = require('./lib');
var Obj = require('./object');
var lexer = require('./lexer');
var compiler = require('./compiler');
var builtin_filters = require('./filters');
var builtin_loaders = require('./loaders');
var runtime = require('./runtime');
var globals = require('./globals');
var Frame = runtime.Frame;

var Environment = Obj.extend({
    init: function(loaders, opts) {
        // The dev flag determines the trace that'll be shown on errors.
        // If set to true, returns the full trace from the error point,
        // otherwise will return trace starting from Template.render
        // (the full trace from within nunjucks may confuse developers using
        //  the library)
        // defaults to false
        opts = opts || {};
        this.dev = !!opts.dev;

        // The autoescape flag sets global autoescaping. If true,
        // every string variable will be escaped by default.
        // If false, strings can be manually escaped using the `escape` filter.
        // defaults to false
        this.autoesc = !!opts.autoescape;

        if(!loaders) {
            // The filesystem loader is only available client-side
            if(builtin_loaders.FileSystemLoader) {
                this.loaders = [new builtin_loaders.FileSystemLoader('views')];
            }
            else {
                this.loaders = [new builtin_loaders.WebLoader('/views')];
            }
        }
        else {
            this.loaders = lib.isArray(loaders) ? loaders : [loaders];
        }

        this.initCache();
        this.filters = {};
        this.asyncFilters = [];
        this.extensions = {};
        this.extensionsList = [];

        if(opts.tags) {
            lexer.setTags(opts.tags);
        }

        for(var name in builtin_filters) {
            this.addFilter(name, builtin_filters[name]);
        }
    },

    initCache: function() {
        // Caching and cache busting
        var cache = {};

        lib.each(this.loaders, function(loader) {
            loader.on('update', function(template) {
                cache[template] = null;
            });
        });

        this.cache = cache;
    },

    addExtension: function(name, extension) {
        extension._name = name;
        this.extensions[name] = extension;
        this.extensionsList.push(extension);
    },

    getExtension: function(name) {
        return this.extensions[name];
    },

    addFilter: function(name, func, async) {
        var wrapped = func;

        if(async) {
            this.asyncFilters.push(name);
        }
        this.filters[name] = wrapped;
    },

    getFilter: function(name) {
        if(!this.filters[name]) {
            throw new Error('filter not found: ' + name);
        }
        return this.filters[name];
    },

    getTemplate: function(name, eagerCompile, cb) {
        if(name && name.raw) {
            // this fixes autoescape for templates referenced in symbols
            name = name.raw;
        }

        if(lib.isFunction(eagerCompile)) {
            cb = eagerCompile;
            eagerCompile = false;
        }

        if(typeof name !== 'string') {
            throw new Error('template names must be a string: ' + name);
        }

        var tmpl = this.cache[name];

        if(tmpl) {
            cb(null, tmpl);
        } else {
            lib.asyncEach(this.loaders, function(loader, i, next, done) {
                function handle(src) {
                    if(src) {
                        done(src);
                    }
                    else {
                        next();
                    }
                }

                if(loader.async) {
                    loader.getSource(name, function(err, src) {
                        if(err) { throw err; }
                        handle(src);
                    });
                }
                else {
                    handle(loader.getSource(name));
                }
            }, function(info) {
                if(!info) {
                    cb(new Error('template not found: ' + name));
                }
                else {
                    var tmpl = new Template(info.src, this,
                                            info.path, eagerCompile);

                    if(!info.noCache) {
                        this.cache[name] = tmpl;
                    }

                    cb(null, tmpl);
                }
            }.bind(this));
        }
    },

    // registerPrecompiled: function(templates) {
    //     for(var name in templates) {
    //         this.cache[name] = new Template({ type: 'code',
    //                                           obj: templates[name] },
    //                                         this,
    //                                         name,
    //                                         function() { return true; },
    //                                         true);
    //     }
    // },

    express: function(app) {
        app.engine('.html', this.expressRender.bind(this));
        app.set('view engine', 'html');

        var env = this;

        function NunjucksView(name, opts) {
            this.name = name;
            this.path = name;
        }

        NunjucksView.prototype.render = function(opts, cb) {
            env.render(this.name, opts, cb);
        };

        app.set('view', NunjucksView);
    },

    expressRender: function(name, opts, cb) {
        this.render(name, {}, cb);
    },

    render: function(name, ctx, cb) {
        this.getTemplate(name, function(err, tmpl) {
            if(err) {
                cb(err);
            }
            else {
                tmpl.render(ctx, cb);
            }
        });
    }
});

var Context = Obj.extend({
    init: function(ctx, blocks) {
        this.ctx = ctx;
        this.blocks = {};
        this.exported = [];

        for(var name in blocks) {
            this.addBlock(name, blocks[name]);
        }
    },

    lookup: function(name) {
        // This is one of the most called functions, so optimize for
        // the typical case where the name isn't in the globals
        if(name in globals && !(name in this.ctx)) {
            return globals[name];
        }
        else {
            return this.ctx[name];
        }
    },

    setVariable: function(name, val) {
        this.ctx[name] = val;
    },

    getVariables: function() {
        return this.ctx;
    },

    addBlock: function(name, block) {
        this.blocks[name] = this.blocks[name] || [];
        this.blocks[name].push(block);
    },

    getBlock: function(name) {
        if(!this.blocks[name]) {
            throw new Error('unknown block "' + name + '"');
        }

        return this.blocks[name][0];
    },

    getSuper: function(env, name, block, frame, runtime, cb) {
        var idx = (this.blocks[name] || []).indexOf(block);
        var blk = this.blocks[name][idx + 1];
        var context = this;

        if(idx == -1 || !blk) {
            throw new Error('no super block available for "' + name + '"');
        }

        blk(env, context, frame, runtime, cb);
    },

    addExport: function(name) {
        this.exported.push(name);
    },

    getExported: function() {
        var exported = {};
        for(var i=0; i<this.exported.length; i++) {
            var name = this.exported[i];
            exported[name] = this.ctx[name];
        }
        return exported;
    }
});

var Template = Obj.extend({
    init: function (src, env, path, eagerCompile) {
        this.env = env || new Environment();

        if(lib.isObject(src)) {
            switch(src.type) {
            case 'code': this.tmplProps = src.obj; break;
            case 'string': this.tmplStr = src.obj; break;
            }
        }
        else if(lib.isString(src)) {
            this.tmplStr = src;
        }
        else {
            throw new Error("src must be a string or an object describing " +
                            "the source");
        }

        this.path = path;

        if(eagerCompile) {
            lib.withPrettyErrors(this.path,
                                 this.env.dev,
                                 this._compile.bind(this));
        }
        else {
            this.compiled = false;
        }
    },

    render: function(ctx, frame, cb) {
        if (typeof ctx === 'function') {
            cb = ctx;
            ctx = {};
        }
        else if (typeof frame === 'function') {
            cb = frame;
            frame = null;
        }

        if(!cb) {
            throw new Error("you must specify a callback to `render`");
        }

        return lib.withPrettyErrors(this.path, this.env.dev, function() {
            if(!this.compiled) {
                this._compile();
            }

            var context = new Context(ctx || {}, this.blocks);

            this.rootRenderFunc(this.env,
                                context,
                                frame || new Frame(),
                                runtime,
                                cb);
        }.bind(this));
    },

    getExported: function(cb) {
        if(!this.compiled) {
            this._compile();
        }

        // Run the rootRenderFunc to populate the context with exported vars
        var context = new Context({}, this.blocks);
        this.rootRenderFunc(this.env,
                            context,
                            new Frame(),
                            runtime,
                            function() {
                                cb(null, context.getExported());
                            });
    },

    _compile: function() {
        var props;

        if(this.tmplProps) {
            props = this.tmplProps;
        }
        else {
            var source = compiler.compile(this.tmplStr,
                                          this.env.asyncFilters,
                                          this.env.extensionsList,
                                          this.path);
            var func = new Function(source);
            props = func();
        }

        this.blocks = this._getBlocks(props);
        this.rootRenderFunc = props.root;
        this.compiled = true;
    },

    _getBlocks: function(props) {
        var blocks = {};

        for(var k in props) {
            if(k.slice(0, 2) == 'b_') {
                blocks[k.slice(2)] = props[k];
            }
        }

        return blocks;
    }
});

// var src = '{% block content %}{% include "async.html" %}{% endblock %}';
// var env = new Environment(new builtin_loaders.FileSystemLoader('tests/templates'), { dev: true });

// var ctx = {};
// var tmpl = new Template(src, env, null, null, true);
// console.log("OUTPUT ---");

// tmpl.render(ctx, function(err, res) {
//     if(err) {
//         throw err;
//     }
//     console.log(res);
// });

module.exports = {
    Environment: Environment,
    Template: Template
};
<commit_msg>experimental support for sync API, for better migration path<commit_after>var lib = require('./lib');
var Obj = require('./object');
var lexer = require('./lexer');
var compiler = require('./compiler');
var builtin_filters = require('./filters');
var builtin_loaders = require('./loaders');
var runtime = require('./runtime');
var globals = require('./globals');
var Frame = runtime.Frame;

var Environment = Obj.extend({
    init: function(loaders, opts) {
        // The dev flag determines the trace that'll be shown on errors.
        // If set to true, returns the full trace from the error point,
        // otherwise will return trace starting from Template.render
        // (the full trace from within nunjucks may confuse developers using
        //  the library)
        // defaults to false
        opts = opts || {};
        this.dev = !!opts.dev;

        // The autoescape flag sets global autoescaping. If true,
        // every string variable will be escaped by default.
        // If false, strings can be manually escaped using the `escape` filter.
        // defaults to false
        this.autoesc = !!opts.autoescape;

        if(!loaders) {
            // The filesystem loader is only available client-side
            if(builtin_loaders.FileSystemLoader) {
                this.loaders = [new builtin_loaders.FileSystemLoader('views')];
            }
            else {
                this.loaders = [new builtin_loaders.WebLoader('/views')];
            }
        }
        else {
            this.loaders = lib.isArray(loaders) ? loaders : [loaders];
        }

        this.initCache();
        this.filters = {};
        this.asyncFilters = [];
        this.extensions = {};
        this.extensionsList = [];

        if(opts.tags) {
            lexer.setTags(opts.tags);
        }

        for(var name in builtin_filters) {
            this.addFilter(name, builtin_filters[name]);
        }
    },

    initCache: function() {
        // Caching and cache busting
        var cache = {};

        lib.each(this.loaders, function(loader) {
            loader.on('update', function(template) {
                cache[template] = null;
            });
        });

        this.cache = cache;
    },

    addExtension: function(name, extension) {
        extension._name = name;
        this.extensions[name] = extension;
        this.extensionsList.push(extension);
    },

    getExtension: function(name) {
        return this.extensions[name];
    },

    addFilter: function(name, func, async) {
        var wrapped = func;

        if(async) {
            this.asyncFilters.push(name);
        }
        this.filters[name] = wrapped;
    },

    getFilter: function(name) {
        if(!this.filters[name]) {
            throw new Error('filter not found: ' + name);
        }
        return this.filters[name];
    },

    getTemplate: function(name, eagerCompile, cb) {
        if(name && name.raw) {
            // this fixes autoescape for templates referenced in symbols
            name = name.raw;
        }

        if(lib.isFunction(eagerCompile)) {
            cb = eagerCompile;
            eagerCompile = false;
        }

        if(typeof name !== 'string') {
            throw new Error('template names must be a string: ' + name);
        }

        var tmpl = this.cache[name];

        if(tmpl) {
            cb(null, tmpl);
        } else {
            lib.asyncEach(this.loaders, function(loader, i, next, done) {
                function handle(src) {
                    if(src) {
                        done(src);
                    }
                    else {
                        next();
                    }
                }

                if(loader.async) {
                    loader.getSource(name, function(err, src) {
                        if(err) { throw err; }
                        handle(src);
                    });
                }
                else {
                    handle(loader.getSource(name));
                }
            }, function(info) {
                if(!info) {
                    cb(new Error('template not found: ' + name));
                }
                else {
                    var tmpl = new Template(info.src, this,
                                            info.path, eagerCompile);

                    if(!info.noCache) {
                        this.cache[name] = tmpl;
                    }

                    cb(null, tmpl);
                }
            }.bind(this));
        }
    },

    express: function(app) {
        var env = this;

        function NunjucksView(name, opts) {
            this.name = name;
            this.path = name;
        }

        NunjucksView.prototype.render = function(opts, cb) {
            cb(null, env.render(this.name, opts));
        };

        app.set('view', NunjucksView);
    },

    render: function(name, ctx, cb) {
        var syncResult = null;

        this.getTemplate(name, function(err, tmpl) {
            if(err) {
                cb(err);
            }
            else {
                tmpl.render(ctx, cb || function(err, res) {
                    if(err) { throw err; }
                    syncResult = res;
                });
            }
        });

        return syncResult;
    }
});

var Context = Obj.extend({
    init: function(ctx, blocks) {
        this.ctx = ctx;
        this.blocks = {};
        this.exported = [];

        for(var name in blocks) {
            this.addBlock(name, blocks[name]);
        }
    },

    lookup: function(name) {
        // This is one of the most called functions, so optimize for
        // the typical case where the name isn't in the globals
        if(name in globals && !(name in this.ctx)) {
            return globals[name];
        }
        else {
            return this.ctx[name];
        }
    },

    setVariable: function(name, val) {
        this.ctx[name] = val;
    },

    getVariables: function() {
        return this.ctx;
    },

    addBlock: function(name, block) {
        this.blocks[name] = this.blocks[name] || [];
        this.blocks[name].push(block);
    },

    getBlock: function(name) {
        if(!this.blocks[name]) {
            throw new Error('unknown block "' + name + '"');
        }

        return this.blocks[name][0];
    },

    getSuper: function(env, name, block, frame, runtime, cb) {
        var idx = (this.blocks[name] || []).indexOf(block);
        var blk = this.blocks[name][idx + 1];
        var context = this;

        if(idx == -1 || !blk) {
            throw new Error('no super block available for "' + name + '"');
        }

        blk(env, context, frame, runtime, cb);
    },

    addExport: function(name) {
        this.exported.push(name);
    },

    getExported: function() {
        var exported = {};
        for(var i=0; i<this.exported.length; i++) {
            var name = this.exported[i];
            exported[name] = this.ctx[name];
        }
        return exported;
    }
});

var Template = Obj.extend({
    init: function (src, env, path, eagerCompile) {
        this.env = env || new Environment();

        if(lib.isObject(src)) {
            switch(src.type) {
            case 'code': this.tmplProps = src.obj; break;
            case 'string': this.tmplStr = src.obj; break;
            }
        }
        else if(lib.isString(src)) {
            this.tmplStr = src;
        }
        else {
            throw new Error("src must be a string or an object describing " +
                            "the source");
        }

        this.path = path;

        if(eagerCompile) {
            lib.withPrettyErrors(this.path,
                                 this.env.dev,
                                 this._compile.bind(this));
        }
        else {
            this.compiled = false;
        }
    },

    render: function(ctx, frame, cb) {
        if (typeof ctx === 'function') {
            cb = ctx;
            ctx = {};
        }
        else if (typeof frame === 'function') {
            cb = frame;
            frame = null;
        }

        return lib.withPrettyErrors(this.path, this.env.dev, function() {
            if(!this.compiled) {
                this._compile();
            }

            var context = new Context(ctx || {}, this.blocks);
            var syncResult = null;

            this.rootRenderFunc(this.env,
                                context,
                                frame || new Frame(),
                                runtime,
                                cb || function(err, res) {
                                    if(err) { throw err; }
                                    syncResult = res;
                                });

            return syncResult;
        }.bind(this));
    },

    getExported: function(cb) {
        if(!this.compiled) {
            this._compile();
        }

        // Run the rootRenderFunc to populate the context with exported vars
        var context = new Context({}, this.blocks);
        this.rootRenderFunc(this.env,
                            context,
                            new Frame(),
                            runtime,
                            function() {
                                cb(null, context.getExported());
                            });
    },

    _compile: function() {
        var props;

        if(this.tmplProps) {
            props = this.tmplProps;
        }
        else {
            var source = compiler.compile(this.tmplStr,
                                          this.env.asyncFilters,
                                          this.env.extensionsList,
                                          this.path);
            var func = new Function(source);
            props = func();
        }

        this.blocks = this._getBlocks(props);
        this.rootRenderFunc = props.root;
        this.compiled = true;
    },

    _getBlocks: function(props) {
        var blocks = {};

        for(var k in props) {
            if(k.slice(0, 2) == 'b_') {
                blocks[k.slice(2)] = props[k];
            }
        }

        return blocks;
    }
});

// var src = '{% block content %}{% include "async.html" %}{% endblock %}';
// var env = new Environment(new builtin_loaders.FileSystemLoader('tests/templates'), { dev: true });

// var ctx = {};
// var tmpl = new Template(src, env, null, null, true);
// console.log("OUTPUT ---");

// tmpl.render(ctx, function(err, res) {
//     if(err) {
//         throw err;
//     }
//     console.log(res);
// });

module.exports = {
    Environment: Environment,
    Template: Template
};
<|endoftext|>


<commit_before>/**
 * @flow
 */

// TODO: This is a hack, we need to find a way to solve that more elegantly. But
// for not this is required as we redirected stderr to stdout and those
// possible deprecation warnings can really hurt and make things difficult to
// debug.
// $FlowFixMe: fix me
process.noDeprecation = true;

import type {BuildTask} from '../types';

import * as fs from 'fs';
import loudRejection from 'loud-rejection';
import outdent from 'outdent';
import userHome from 'user-home';
import * as path from 'path';
import chalk from 'chalk';
import {settings as configureObservatory} from 'observatory';
import * as Env from '../environment';
import * as Task from '../build-task';
import * as Config from '../build-config';
import * as Sandbox from '../build-sandbox';

/**
 * Each package can configure exportedEnvVars with:
 *
 * (object key is environment variable name)
 *
 * val: string
 *
 * scope: In short:
 *    "local": Can be seen by this package at build time, shadows anything
 *    configured by dependencies.
 *    "export": Seen by immediate dependers during their build times, and
 *    shadows any global variables those immediate dependers can see at build
 *    time.
 *    "global": Seen by all packages that have a transitive linktime dependency
 *    on our package.
 *
 *    You can or them together: "local|export", "local|global".
 *
 * Example:
 *
 *   If you are publishing a binary to all transitive dependers, you'd do:
 *
 *     "PATH": {
 *       "val": "PATH:$PATH",
 *       "scope": "global"
 *     }
 *
 *   You wouldn't necessarily use a "local" scope because your package that
 *   builds the resulting binary doesn't care about seeing that binary.
 *
 *   Similarly, if you build library artifacts, you don't care about *seeing*
 *   those library artifacts as the library that is building them.
 *
 *
 *     "FINDLIB": {
 *       "val": "$MY_PACKAGE__LIB:$FINDLIB",
 *       "scope": "export"
 *     }
 *
 * VISIBILITY:
 * -------------
 *
 * Consider that a package my-compiler has defines a variable CC_FLAG. It would
 * normally publish some default flag with a "global" scope so that everyone
 * who transitively depends on it can see the default.
 *
 * "CC_FLAG": {
 *   "val": "-default-flag",
 *   "scope": "global"
 * }
 *
 * Then we want to be able to create a package `my-package` that depends on
 * `my-compiler`, which wants to override those flags for its own package
 * compilation - so it sets the scope flag to "local". The local scope
 * shadows the global scope, and the new value is only observed by
 * `my-package`.
 *
 * "CC_FLAG": {
 *   "val": "-opt 0",
 *   "scope": "local"
 * }
 *
 * In the same way that let bindings shadow global bindings, yet can reference
 * the global one in the definition of the local one, the same is true of local
 * environment variables.
 *
 *   let print_string = fun(s) => print_string(s + "!!!");
 *
 *   // Analogous to
 *   "CC_FLAG": {
 *     "val": "-opt 0 $CC_FLAG",
 *     "scope": "local"
 *   }
 *
 *
 * Local scopes allow us to create a package `my-app` that depends on
 * `my-package` (which in turn depends on `my-compiler`) such that `my-app`
 * doesn't observe the conpiler flags that its dependency (`my-package`) used.
 *
 * Though, in other cases, we *do* want configured flags to be visible.
 * Imagine making a package called `add-opt-flags`, which only has a
 * `package.json` that configures optimized compiler flags. If you directly
 * depend on `add-opt-flags`, you get all the flags added to your package.
 * `add-opt-flags` would configure the variable like:
 *
 * "CC_FLAG": {
 *   "val": "-opt 3",
 *   "scope": "export"
 * }
 *
 * If `your-app` depends on `add-opt-flags`, you would get all the flags set by
 * `add-opt-flags`, but if `app-store` depends on `your-app`, `app-store`
 * wouldn't have opt flags added automatically.
 *
 *
 * Priority of scope visibility is as follows: You see the global scope
 * (consisting of all global variables set by your transitive dependencies)
 * then you see the exported scope of your direct dependencies, shadowing any
 * global scope and then you see your local scope, which shaddows everything
 * else. Each time you shadow a scope, you can reference the lower priority
 * scope *while* shadowing. Just like you can do the following in ML, to
 * redefine addition in terms of addition that was in global scope.
 *
 * A language analogy would be the assumption that every package has an implicit
 * "opening" of its dependencies' exports, to bring them into scope.
 *
 *   open GlobalScopeFromAllTransitiveRuntimeDependencies;
 *   open AllImmediateDependencies.Exports;
 *
 *   let myLocalVariable = expression(in, terms, of, everything, above);
 *
 * In fact, all of this configuration could/should be replaced by a real
 * language. The package builder would then just be something that concatenates
 * files together in a predictable order.
 *
 * WHO CAN WRITE:
 * -------------
 *
 *  When thinking about conflicts, it helps to recall that different scopes are
 *  actually writing to different locations that shadow in convenient ways.
 *  We need some way to control exclusivity of writing these env vars to prevent
 *  conflicts. The current implementaiton just has a single exclusive:
 *  true/false flag and it doesn't take into account scope.
 */

function formatError(message: string, stack?: string) {
  let result = `${chalk.red('ERROR')} ${message}`;
  if (stack != null) {
    result += `\n${stack}`;
  }
  return result;
}

function error(error: Error | string) {
  const message = String(error.message ? error.message : error);
  const stack = error.stack ? String(error.stack) : undefined;
  console.log(formatError(message, stack));
  process.exit(1);
}

async function getValidSandbox(directory) {
  const sandbox = await Sandbox.fromDirectory(directory);
  if (sandbox.root.errors.length > 0) {
    sandbox.root.errors.forEach(error => {
      console.log(formatError(error.message));
    });
    process.exit(1);
  }
  return sandbox;
}

const actualArgs = process.argv.slice(2);
// TODO: Need to change this to climb to closest package.json.
const sandboxPath = process.cwd();
const storePath = process.env.ESY__STORE || path.join(userHome, '.esy', 'store');
const config = Config.createConfig({storePath, sandboxPath});

async function buildCommand(sandboxPath) {
  const builder = require('../builders/simple-builder');

  const observatory = configureObservatory({
    prefix: chalk.green('  → '),
  });

  const loggingHandlers = new Map();
  function getReporterFor(task) {
    let handler = loggingHandlers.get(task.id);
    if (handler == null) {
      handler = observatory.add(task.spec.name);
      loggingHandlers.set(task.id, handler);
    }
    return handler;
  }

  const sandbox = await getValidSandbox(sandboxPath);
  const task: BuildTask = Task.fromBuildSandbox(sandbox, config);
  const failures = [];
  await builder.build(task, sandbox, config, (task, status) => {
    if (status.state === 'in-progress') {
      getReporterFor(task).status('building...');
    } else if (status.state === 'success') {
      const {timeEllapsed} = status;
      if (timeEllapsed != null) {
        getReporterFor(task).done('BUILT').details(`in ${timeEllapsed / 1000}s`);
      } else if (!task.spec.shouldBePersisted) {
        getReporterFor(task).done('BUILT').details(`no source change detected`);
      }
    } else if (status.state === 'failure') {
      failures.push({task, error: status.error});
      getReporterFor(task).fail('FAILED');
    }
  });
  for (const failure of failures) {
    const {error} = failure;
    if (error.logFilename) {
      const {logFilename} = (error: any);
      if (!failure.task.spec.shouldBePersisted) {
        const logContents = fs.readFileSync(logFilename, 'utf8');
        console.log(
          outdent`

            ${chalk.red('FAILED')} ${failure.task.spec.name}, see log for details:

            ${chalk.red(indent(logContents, '  '))}
            `,
        );
      } else {
        console.log(
          outdent`

            ${chalk.red('FAILED')} ${failure.task.spec.name}, see log for details:
              ${logFilename}

            `,
        );
      }
    } else {
      console.log(
        outdent`

        ${chalk.red('FAILED')} ${failure.task.spec.name}:
          ${failure.error}

        `,
      );
    }
  }
}

async function buildEjectCommand(sandboxPath) {
  const buildEject = require('../builders/makefile-builder');
  const sandbox = await getValidSandbox(sandboxPath);
  buildEject.renderToMakefile(
    sandbox,
    path.join(sandboxPath, 'node_modules', '.cache', '_esy', 'build-eject'),
  );
}

const builtInCommands = {
  'build-eject': buildEjectCommand,
  build: buildCommand,
};

function indent(string, indent) {
  return string.split('\n').map(line => indent + line).join('\n');
}

async function main() {
  if (actualArgs.length === 0) {
    // TODO: It's just a status command. Print the command that would be
    // used to setup the environment along with status of
    // the build processes, staleness, package validity etc.
    const sandbox = await getValidSandbox(sandboxPath);
    const task = Task.fromBuildSandbox(sandbox, config);
    // Sandbox env is more strict than we want it to be at runtime, filter
    // out $SHELL overrides.
    task.env.delete('SHELL');
    console.log(Env.printEnvironment(task.env));
  } else {
    const builtInCommandName = actualArgs[0];
    const builtInCommand = builtInCommands[builtInCommandName];
    if (builtInCommand) {
      await builtInCommand(sandboxPath, ...process.argv.slice(3));
    } else {
      console.error(`unknown command: ${builtInCommandName}`);
    }
  }
}

main().catch(error);
loudRejection();
<commit_msg>Less verbose output for status<commit_after>/**
 * @flow
 */

// TODO: This is a hack, we need to find a way to solve that more elegantly. But
// for not this is required as we redirected stderr to stdout and those
// possible deprecation warnings can really hurt and make things difficult to
// debug.
// $FlowFixMe: fix me
process.noDeprecation = true;

import type {BuildTask} from '../types';

import * as fs from 'fs';
import loudRejection from 'loud-rejection';
import outdent from 'outdent';
import userHome from 'user-home';
import * as path from 'path';
import chalk from 'chalk';
import {settings as configureObservatory} from 'observatory';
import * as Env from '../environment';
import * as Task from '../build-task';
import * as Config from '../build-config';
import * as Sandbox from '../build-sandbox';

/**
 * Each package can configure exportedEnvVars with:
 *
 * (object key is environment variable name)
 *
 * val: string
 *
 * scope: In short:
 *    "local": Can be seen by this package at build time, shadows anything
 *    configured by dependencies.
 *    "export": Seen by immediate dependers during their build times, and
 *    shadows any global variables those immediate dependers can see at build
 *    time.
 *    "global": Seen by all packages that have a transitive linktime dependency
 *    on our package.
 *
 *    You can or them together: "local|export", "local|global".
 *
 * Example:
 *
 *   If you are publishing a binary to all transitive dependers, you'd do:
 *
 *     "PATH": {
 *       "val": "PATH:$PATH",
 *       "scope": "global"
 *     }
 *
 *   You wouldn't necessarily use a "local" scope because your package that
 *   builds the resulting binary doesn't care about seeing that binary.
 *
 *   Similarly, if you build library artifacts, you don't care about *seeing*
 *   those library artifacts as the library that is building them.
 *
 *
 *     "FINDLIB": {
 *       "val": "$MY_PACKAGE__LIB:$FINDLIB",
 *       "scope": "export"
 *     }
 *
 * VISIBILITY:
 * -------------
 *
 * Consider that a package my-compiler has defines a variable CC_FLAG. It would
 * normally publish some default flag with a "global" scope so that everyone
 * who transitively depends on it can see the default.
 *
 * "CC_FLAG": {
 *   "val": "-default-flag",
 *   "scope": "global"
 * }
 *
 * Then we want to be able to create a package `my-package` that depends on
 * `my-compiler`, which wants to override those flags for its own package
 * compilation - so it sets the scope flag to "local". The local scope
 * shadows the global scope, and the new value is only observed by
 * `my-package`.
 *
 * "CC_FLAG": {
 *   "val": "-opt 0",
 *   "scope": "local"
 * }
 *
 * In the same way that let bindings shadow global bindings, yet can reference
 * the global one in the definition of the local one, the same is true of local
 * environment variables.
 *
 *   let print_string = fun(s) => print_string(s + "!!!");
 *
 *   // Analogous to
 *   "CC_FLAG": {
 *     "val": "-opt 0 $CC_FLAG",
 *     "scope": "local"
 *   }
 *
 *
 * Local scopes allow us to create a package `my-app` that depends on
 * `my-package` (which in turn depends on `my-compiler`) such that `my-app`
 * doesn't observe the conpiler flags that its dependency (`my-package`) used.
 *
 * Though, in other cases, we *do* want configured flags to be visible.
 * Imagine making a package called `add-opt-flags`, which only has a
 * `package.json` that configures optimized compiler flags. If you directly
 * depend on `add-opt-flags`, you get all the flags added to your package.
 * `add-opt-flags` would configure the variable like:
 *
 * "CC_FLAG": {
 *   "val": "-opt 3",
 *   "scope": "export"
 * }
 *
 * If `your-app` depends on `add-opt-flags`, you would get all the flags set by
 * `add-opt-flags`, but if `app-store` depends on `your-app`, `app-store`
 * wouldn't have opt flags added automatically.
 *
 *
 * Priority of scope visibility is as follows: You see the global scope
 * (consisting of all global variables set by your transitive dependencies)
 * then you see the exported scope of your direct dependencies, shadowing any
 * global scope and then you see your local scope, which shaddows everything
 * else. Each time you shadow a scope, you can reference the lower priority
 * scope *while* shadowing. Just like you can do the following in ML, to
 * redefine addition in terms of addition that was in global scope.
 *
 * A language analogy would be the assumption that every package has an implicit
 * "opening" of its dependencies' exports, to bring them into scope.
 *
 *   open GlobalScopeFromAllTransitiveRuntimeDependencies;
 *   open AllImmediateDependencies.Exports;
 *
 *   let myLocalVariable = expression(in, terms, of, everything, above);
 *
 * In fact, all of this configuration could/should be replaced by a real
 * language. The package builder would then just be something that concatenates
 * files together in a predictable order.
 *
 * WHO CAN WRITE:
 * -------------
 *
 *  When thinking about conflicts, it helps to recall that different scopes are
 *  actually writing to different locations that shadow in convenient ways.
 *  We need some way to control exclusivity of writing these env vars to prevent
 *  conflicts. The current implementaiton just has a single exclusive:
 *  true/false flag and it doesn't take into account scope.
 */

function formatError(message: string, stack?: string) {
  let result = `${chalk.red('ERROR')} ${message}`;
  if (stack != null) {
    result += `\n${stack}`;
  }
  return result;
}

function error(error: Error | string) {
  const message = String(error.message ? error.message : error);
  const stack = error.stack ? String(error.stack) : undefined;
  console.log(formatError(message, stack));
  process.exit(1);
}

async function getValidSandbox(directory) {
  const sandbox = await Sandbox.fromDirectory(directory);
  if (sandbox.root.errors.length > 0) {
    sandbox.root.errors.forEach(error => {
      console.log(formatError(error.message));
    });
    process.exit(1);
  }
  return sandbox;
}

const actualArgs = process.argv.slice(2);
// TODO: Need to change this to climb to closest package.json.
const sandboxPath = process.cwd();
const storePath = process.env.ESY__STORE || path.join(userHome, '.esy', 'store');
const config = Config.createConfig({storePath, sandboxPath});

async function buildCommand(sandboxPath) {
  const builder = require('../builders/simple-builder');

  const observatory = configureObservatory({
    prefix: chalk.green('  → '),
  });

  const loggingHandlers = new Map();
  function getReporterFor(task) {
    let handler = loggingHandlers.get(task.id);
    if (handler == null) {
      handler = observatory.add(task.spec.name);
      loggingHandlers.set(task.id, handler);
    }
    return handler;
  }

  const sandbox = await getValidSandbox(sandboxPath);
  const task: BuildTask = Task.fromBuildSandbox(sandbox, config);
  const failures = [];
  await builder.build(task, sandbox, config, (task, status) => {
    if (status.state === 'in-progress') {
      getReporterFor(task).status('building...');
    } else if (status.state === 'success') {
      const {timeEllapsed} = status;
      if (timeEllapsed != null) {
        getReporterFor(task).done('BUILT').details(`in ${timeEllapsed / 1000}s`);
      } else if (!task.spec.shouldBePersisted) {
        getReporterFor(task).done('OK').details(`unchanged`);
      }
    } else if (status.state === 'failure') {
      failures.push({task, error: status.error});
      getReporterFor(task).fail('FAILED');
    }
  });
  for (const failure of failures) {
    const {error} = failure;
    if (error.logFilename) {
      const {logFilename} = (error: any);
      if (!failure.task.spec.shouldBePersisted) {
        const logContents = fs.readFileSync(logFilename, 'utf8');
        console.log(
          outdent`

            ${chalk.red('FAILED')} ${failure.task.spec.name}, see log for details:

            ${chalk.red(indent(logContents, '  '))}
            `,
        );
      } else {
        console.log(
          outdent`

            ${chalk.red('FAILED')} ${failure.task.spec.name}, see log for details:
              ${logFilename}

            `,
        );
      }
    } else {
      console.log(
        outdent`

        ${chalk.red('FAILED')} ${failure.task.spec.name}:
          ${failure.error}

        `,
      );
    }
  }
}

async function buildEjectCommand(sandboxPath) {
  const buildEject = require('../builders/makefile-builder');
  const sandbox = await getValidSandbox(sandboxPath);
  buildEject.renderToMakefile(
    sandbox,
    path.join(sandboxPath, 'node_modules', '.cache', '_esy', 'build-eject'),
  );
}

const builtInCommands = {
  'build-eject': buildEjectCommand,
  build: buildCommand,
};

function indent(string, indent) {
  return string.split('\n').map(line => indent + line).join('\n');
}

async function main() {
  if (actualArgs.length === 0) {
    // TODO: It's just a status command. Print the command that would be
    // used to setup the environment along with status of
    // the build processes, staleness, package validity etc.
    const sandbox = await getValidSandbox(sandboxPath);
    const task = Task.fromBuildSandbox(sandbox, config);
    // Sandbox env is more strict than we want it to be at runtime, filter
    // out $SHELL overrides.
    task.env.delete('SHELL');
    console.log(Env.printEnvironment(task.env));
  } else {
    const builtInCommandName = actualArgs[0];
    const builtInCommand = builtInCommands[builtInCommandName];
    if (builtInCommand) {
      await builtInCommand(sandboxPath, ...process.argv.slice(3));
    } else {
      console.error(`unknown command: ${builtInCommandName}`);
    }
  }
}

main().catch(error);
loudRejection();
<|endoftext|>