UNPKG

literate-elm

Version:

Runs literate Elm code blocks and calculates Elm expressions

319 lines 13.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runProgram = void 0; const elm_string_representation_1 = require("elm-string-representation"); const fs_extra_1 = require("fs-extra"); const lodash_1 = __importDefault(require("lodash")); const object_hash_1 = __importDefault(require("object-hash")); const path_1 = require("path"); const auxFiles = __importStar(require("./shared/auxFiles")); const tools_1 = require("./shared/tools"); const programTimeout = 20000; const outputSymbolName = "literateElmOutputSymbol"; const chunkifyProgram = (program) => { const chunks = []; lodash_1.default.forEach(program.codeNodes, (codeNode, i) => { chunks.push({ type: "codeNode", ref: codeNode, text: `-------- literate-elm code ${i}\n${codeNode.text}`, }); }); const expressionNodesGroupedByText = lodash_1.default.groupBy(program.expressionNodes, (expressionNode) => expressionNode.text); const orderedExpressionTexts = lodash_1.default.sortBy(lodash_1.default.keys(expressionNodesGroupedByText)); chunks.push({ type: "auxiliary", text: `-------- literate-elm output ${outputSymbolName} : String ${outputSymbolName} = Json.Encode.encode 0 <| Json.Encode.object [`, }); lodash_1.default.forEach(orderedExpressionTexts, (text, i) => { chunks.push({ type: "expressionText", ref: expressionNodesGroupedByText[text], text: `-------- literate-elm output expression ${text} ${i > 0 ? "," : " "} ("${text.replace(/"/g, '\\"')}", Json.Encode.string <| Debug.toString <| ${text})`, }); }); chunks.push({ type: "auxiliary", text: `-------- literate-elm output end\n ]\n`, }); // only import Json.Encode if not done so in user code const containsJsonEncodeImport = lodash_1.default.some(chunks, (codeChunk) => `\n${codeChunk.text}`.match(/\n\s*import\s+Json\.Encode/)); if (!containsJsonEncodeImport) { chunks.unshift({ type: "auxiliary", text: `import Json.Encode`, }); } const programName = `Program${(0, object_hash_1.default)(chunks.map((chunk) => chunk.text))}`; chunks.unshift({ type: "auxiliary", text: `module ${programName} exposing (..)`, }); // measure vertical offset for each code chunk to map errors later let offsetY = 0; lodash_1.default.forEach(chunks, (chunk) => { const lineCount = (chunk.text.match(/\n/g) || []).length + 1; chunk.offsetY = offsetY; offsetY += lineCount; }); return { name: programName, chunks, maxFileIndex: Math.max(...lodash_1.default.map([...program.codeNodes, ...program.expressionNodes], (node) => node.fileIndex || 0)), }; }; const runChunkifiedProgram = (chunkifiedProgram, workingDirectory, keepElmFiles = false) => __awaiter(void 0, void 0, void 0, function* () { const programPath = (0, path_1.resolve)(workingDirectory, `${chunkifiedProgram.name}.elm`); const codeToRun = chunkifiedProgram.chunks.map(({ text }) => text).join("\n"); try { let runElmResult; yield (0, fs_extra_1.writeFile)(programPath, codeToRun, "utf8"); try { runElmResult = yield (0, tools_1.runElm)(workingDirectory, programPath, outputSymbolName); } catch (e) { const linesInStderr = (e.message || "").split("\n"); let parsedErrorOutput; lodash_1.default.findLast(linesInStderr, (line) => { try { parsedErrorOutput = JSON.parse(line); return true; } catch (_a) { return false; } }); if (parsedErrorOutput && lodash_1.default.isArray(parsedErrorOutput.errors)) { return { status: "failed", errors: parsedErrorOutput.errors, }; } else if (parsedErrorOutput) { return { status: "failed", errors: [parsedErrorOutput], }; } else { throw e; } } return { status: "succeeded", errors: [], evaluatedExpressionTextMap: JSON.parse(runElmResult.output || "{}"), debugLog: runElmResult.debugLog, }; } catch (e) { // some messages need to be patched to avoid confusing output const message = (e.message || "") .replace(/^Compilation failed\n/, "") .replace(/\n{2,}/, "\n"); const indexOfFirstNewline = message.indexOf("\n"); const overview = indexOfFirstNewline !== -1 ? message.substring(0, indexOfFirstNewline) : message; const details = indexOfFirstNewline !== -1 ? message.substring(indexOfFirstNewline + 1) : ""; return { status: "failed", errors: [ { overview, details, region: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 }, }, }, ], }; } finally { if (!keepElmFiles) { yield (0, fs_extra_1.remove)(programPath); } } }); const getErrorMessageText = (error) => { if (error.title === "UNKNOWN IMPORT") { // Unknown imports form a special case. It is not reasonable to suggest looking into elm.json and source-directories // as this is causing confusion in the literate-elm environment. const failedImport = lodash_1.default.get(error, ["message", 1, "string"]); if (failedImport) { return `Could not ${failedImport}. Please make sure you have specified all dependencies on third-party Elm modules.`; } } if (lodash_1.default.isArray(error.message)) { const text = error.message .map((chunk) => { if (chunk.string) { // remove underlines with ^^^^ if (chunk.string === "^".repeat(chunk.string.length)) { return "__REMOVED_UNDERNLINE__"; } return chunk.string; } return chunk; }) .join(""); return text .replace(/\s*__REMOVED_UNDERNLINE__\s*/g, "\n") .replace(/\n\d+\|/g, "\n"); // remove line numbers in listings } return `${error.overview || error}`; }; const convertErrorsToMessages = (chunkifiedProgram, errors) => { const result = []; lodash_1.default.map(lodash_1.default.get(errors, [0, "problems"], errors), (error) => { const currentChunk = chunkifiedProgram.chunks[lodash_1.default.findIndex(chunkifiedProgram.chunks, (chunk) => (chunk.offsetY || 0) + 1 > lodash_1.default.get(error, ["region", "start", "line"], 0)) - 1]; if (currentChunk && currentChunk.type === "codeNode") { const position = { start: { line: lodash_1.default.get(error, ["region", "start", "line"], 0) - (currentChunk.offsetY || 0) - 1 + currentChunk.ref.position.start.line, column: lodash_1.default.get(error, ["region", "start", "column"], 0) - 1 + currentChunk.ref.position.start.column, }, end: { line: lodash_1.default.get(error, ["region", "end", "line"], 0) - (currentChunk.offsetY || 0) - 1 + currentChunk.ref.position.start.line, column: lodash_1.default.get(error, ["region", "end", "column"], 0) - 1 + currentChunk.ref.position.start.column, }, }; result.push({ text: getErrorMessageText(error), position, severity: "error", fileIndex: currentChunk.ref.fileIndex || 0, node: currentChunk.ref, }); } else if (currentChunk && currentChunk.type === "expressionText") { const messageText = getErrorMessageText(error); const expressionNodes = currentChunk.ref; lodash_1.default.forEach(expressionNodes, (expressionNode) => { result.push({ text: messageText, position: expressionNode.position, fileIndex: expressionNode.fileIndex || 0, severity: "error", node: expressionNode, }); }); } else { result.push({ text: getErrorMessageText(error), severity: "error", position: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 }, }, fileIndex: chunkifiedProgram.maxFileIndex, node: null, }); } }); return result; }; const runProgram = (program) => __awaiter(void 0, void 0, void 0, function* () { const chunkifiedProgram = chunkifyProgram(program); const programBasePath = (0, path_1.resolve)(program.environment.workingDirectory, chunkifiedProgram.name); yield auxFiles.touch(programBasePath); const programResultPath = `${programBasePath}.result.json`; let cachedResult; try { yield auxFiles.ensureUnlocked(programBasePath, programTimeout); cachedResult = (yield (0, fs_extra_1.readJson)(programResultPath)); } catch (e) { yield auxFiles.lock(programBasePath); cachedResult = yield runChunkifiedProgram(chunkifiedProgram, program.environment.workingDirectory); yield (0, fs_extra_1.writeFile)(programResultPath, JSON.stringify(cachedResult), "utf8"); yield auxFiles.unlock(programBasePath); } if (cachedResult && cachedResult.status === "succeeded") { const evaluatedExpressions = program.expressionNodes.map((expressionNode) => { const valueStringRepresentation = (cachedResult.evaluatedExpressionTextMap && cachedResult.evaluatedExpressionTextMap[expressionNode.text]) || ""; let value; try { value = (0, elm_string_representation_1.parseUsingCache)(valueStringRepresentation); } catch (e) { value = e; } return { node: expressionNode, value, valueStringRepresentation, }; }); return { program, status: "succeeded", evaluatedExpressions, messages: convertErrorsToMessages(chunkifiedProgram, (cachedResult && cachedResult.errors) || []), debugLog: cachedResult.debugLog instanceof Array ? cachedResult.debugLog : [], }; } else { return { program, status: "failed", messages: convertErrorsToMessages(chunkifiedProgram, (cachedResult && cachedResult.errors) || []), }; } }); exports.runProgram = runProgram; //# sourceMappingURL=runProgram.js.map