UNPKG

derw

Version:

An Elm-inspired language that transpiles to TypeScript

352 lines (351 loc) 15.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.compileFiles = compileFiles; const baner_1 = require("@eeue56/baner"); const child_process_1 = require("child_process"); const chokidar = __importStar(require("chokidar")); const fs_1 = require("fs"); const promises_1 = require("fs/promises"); const path_1 = __importDefault(require("path")); const util = __importStar(require("util")); const Generator_1 = require("../Generator"); const names_1 = require("../errors/names"); const derwParser = __importStar(require("../parser")); const types_1 = require("../types"); const install_1 = require("./install"); const utils_1 = require("./utils"); const compileParser = (0, baner_1.parser)([ (0, baner_1.longFlag)("files", "File names or folders to be compiled", (0, baner_1.variableList)((0, baner_1.string)())), (0, baner_1.longFlag)("target", "Target TS, JS, Derw, Elm, or English output", (0, baner_1.oneOf)(["ts", "js", "derw", "elm", "english"])), (0, baner_1.longFlag)("output", "Output directory name", (0, baner_1.string)()), (0, baner_1.longFlag)("verify", "Run typescript compiler on generated files to ensure valid output", (0, baner_1.empty)()), (0, baner_1.longFlag)("debug", "Show a parsed object tree", (0, baner_1.empty)()), (0, baner_1.longFlag)("only", "Only show a particular object", (0, baner_1.string)()), (0, baner_1.longFlag)("run", "Should be run via ts-node/node", (0, baner_1.empty)()), (0, baner_1.longFlag)("names", "Check for missing names out of scope", (0, baner_1.empty)()), (0, baner_1.longFlag)("check", "Only run typechecking, not file generation", (0, baner_1.empty)()), (0, baner_1.longFlag)("watch", "Watch the files for changes", (0, baner_1.empty)()), (0, baner_1.longFlag)("quiet", "Keep it short and sweet", (0, baner_1.empty)()), (0, baner_1.bothFlag)("h", "help", "This help text", (0, baner_1.empty)()), ]); function showCompileHelp() { console.log("Let's write some Derw code"); console.log("To get started:"); console.log("Initialize the current directory via `init`"); console.log("Or provide entry files via `--files`"); console.log("Or run me without args inside a package directory"); console.log((0, baner_1.help)(compileParser)); } function getImports(module) { return module.body.filter((block) => block.kind === "Import"); } function runFile(target, fullName) { let child; switch (target) { case "js": { child = (0, child_process_1.spawnSync)(`npx`, [`node`, `${fullName}`], { stdio: "inherit", encoding: "utf-8", }); break; } case "ts": { child = (0, child_process_1.spawnSync)(`npx`, [`ts-node`, `${fullName}`], { stdio: "inherit", encoding: "utf-8", }); break; } } } function filterBodyForName(module, name) { const blocks = []; for (var element of module.body) { switch (element.kind) { case "Function": case "Const": { if (element.name === name) { blocks.push(element); } break; } case "Import": { for (var module_ of element.modules) { if (module_.name === name) { blocks.push(element); break; } } break; } case "UnionType": case "TypeAlias": { if (element.type.name === name) { blocks.push(element); } break; } } } return blocks; } async function compileFiles(isInPackageDirectory, argv) { const program = (0, baner_1.parse)(compileParser, argv); if (program.flags["h/help"].isPresent) { showCompileHelp(); return {}; } const errors = (0, baner_1.allErrors)(program); if (errors.length > 0) { console.log("Errors:"); console.log(errors.join("\n")); process.exit(1); } if (!program.flags.files.isPresent && !isInPackageDirectory) { console.log("You must provide at least one file via --files"); console.log("Or be in a directory with derw-package.json."); process.exit(1); } const doesDerwPackagesFolderExist = await (0, utils_1.fileExists)("derw-packages"); if (!program.flags.files.isPresent && !doesDerwPackagesFolderExist) { console.log("No derw-packages folder found, running install..."); await (0, install_1.install)(isInPackageDirectory, argv); } const debugMode = program.flags["debug"].isPresent; const isPackageDirectoryAndNoFilesPassed = isInPackageDirectory && !program.flags.files.isPresent; const maybeFiles = isPackageDirectoryAndNoFilesPassed ? await (0, utils_1.getDerwFiles)("./src") : await (0, utils_1.getFlatFiles)(program.flags.files.arguments.value); if (maybeFiles.kind === "Err") { const filesToFind = isPackageDirectoryAndNoFilesPassed ? ["./src"] : program.flags.files.arguments.value; for (const file of filesToFind) { const suggestion = await (0, utils_1.suggestFileNames)(file); if (suggestion !== file) { console.error(suggestion); } } process.exit(1); } const files = maybeFiles.value; const outputDir = program.flags.output.isPresent ? program.flags.output.arguments.value : "./"; const isStdout = outputDir === "/dev/stdout"; const isCheck = outputDir === "/dev/null" || program.flags.check.isPresent; if (!isStdout && !isCheck) { await (0, utils_1.ensureDirectoryExists)(outputDir); } const target = program.flags.target.isPresent ? program.flags.target.arguments.value : "ts"; const shouldRun = program.flags.run.isPresent; if (shouldRun && !program.flags.files.isPresent) { console.log(`Warning: not running files. Provide files via --files to run them`); } const isQuiet = program.flags.quiet.isPresent; if (!isQuiet) { if (isInPackageDirectory) { console.log("Compiling package..."); } console.log(`Generating ${files.length} files...`); } async function rawCompile() { const processedFiles = []; const parsedFiles = {}; const parsedImports = {}; for (const fileName of files) { await (async function compile(fileName) { if (processedFiles.indexOf(fileName) > -1) { return; } const isPackageFile = fileName.startsWith("derw-packages"); processedFiles.push(fileName); const dotParts = fileName.split("."); const extension = dotParts[dotParts.length - 1]; if (extension !== "derw") { console.log("Warning: Derw files should be called .derw"); console.log(`Try renaming ${fileName} to ${dotParts.slice(0, -1).join(".")}.derw`); } const derwContents = (await fs_1.promises.readFile(fileName)).toString(); let parsed = derwParser.parseWithContext(derwContents, fileName); if (program.flags.names.isPresent) { parsed = (0, names_1.addMissingNamesSuggestions)(parsed); } parsedFiles[fileName] = parsed; if (parsed.errors.length > 0) { console.log(`Failed to parse ${fileName} due to:`); console.log(parsed.errors.join("\n")); return; } const dir = path_1.default.dirname(fileName); const imports = []; getImports(parsed).forEach((import_) => { import_ = import_; import_.modules.forEach((module) => { if (module.namespace === "Global") return; if (isPackageFile) { if (module.name.startsWith('"../derw-packages')) { module.name = module.name.replace("../derw-packages", "../../.."); } } const moduleName = module.name.slice(1, -1); imports.push(path_1.default.normalize(path_1.default.join(dir, moduleName))); }); }); parsedImports[fileName] = []; for (const import_ of imports) { const fileWithDerwExtension = import_.endsWith(".derw") ? import_ : import_ + `.derw`; const isDerw = await (0, utils_1.fileExists)(fileWithDerwExtension); if (isDerw) { parsedImports[fileName].push(fileWithDerwExtension); await compile(fileWithDerwExtension); continue; } // check if ts/js versions of the file exist const fileWithTsExtension = import_ + `.ts`; const fileWithJsExtension = import_ + `.js`; let doesFileExist = false; if (await (0, utils_1.fileExists)(fileWithTsExtension)) { doesFileExist = true; } else if (await (0, utils_1.fileExists)(fileWithJsExtension)) { doesFileExist = true; } if (!doesFileExist) { console.log(`Warning! Failed to find \`${import_}\` as either derw, ts or js`); } } if (debugMode) { if (program.flags["only"].isPresent) { if (program.flags["only"].arguments.kind === "Err") { console.log(program.flags.only.arguments.error); } else { const name = program.flags["only"].arguments.value; const blocks = filterBodyForName(parsed, name); console.log(`Filtering for ${name}...`); console.log(util.inspect(blocks, true, null, true)); } return; } console.log(util.inspect(parsed, true, null, true)); return; } const importedContextModules = []; for (const import_ of parsedImports[fileName]) { importedContextModules.push(parsedFiles[import_]); } parsed = derwParser.addTypeErrors(parsed, importedContextModules); if (parsed.errors.length > 0) { console.log(`Failed to parse ${fileName} due to:`); console.log(parsed.errors.join("\n")); return; } const generated = (0, Generator_1.generate)(target, (0, types_1.contextModuleToModule)(parsed)); if (program.flags.verify.isPresent && target === "ts") { const { compileTypescript } = await Promise.resolve().then(() => __importStar(require("../compile"))); const output = compileTypescript(generated); if (output.kind === "Err") { console.log(`Failed to compile ${fileName} due to`, output.error.map((e) => e.messageText).join("\n")); } else { console.log(`Successfully compiled ${fileName}`); } } if (isCheck) { return; } if (isStdout) { console.log(generated); return; } if (fileName.indexOf("/") > -1) { const dirName = fileName.split("/").slice(0, -1).join("/"); await (0, utils_1.ensureDirectoryExists)(path_1.default.join(outputDir, dirName)); } const outputName = dotParts.slice(0, -1).join(".") + "." + target; const fullName = path_1.default.join(outputDir, outputName); await (0, promises_1.writeFile)(fullName, generated); if (shouldRun) { const isFileToRun = program.flags.files.isPresent && program.flags.files.arguments.kind === "Ok" && program.flags.files.arguments.value.indexOf(fileName) > -1; if (isFileToRun) { if (!isQuiet) console.log(`Running... ${fullName}`); runFile(target, fullName); } } })(fileName); } if (!isQuiet) { console.log("Processed:", processedFiles); } return parsedFiles; } if (program.flags.watch.isPresent) { if (!isQuiet) console.log("Watching src and derw-packages..."); let timer; chokidar .watch([ path_1.default.join(process.cwd(), "src"), path_1.default.join(process.cwd(), "derw-packages"), ]) .on("all", async (event, path) => { if (path.endsWith(".derw")) { if (timer !== null) { clearTimeout(timer); } timer = setTimeout(async () => { await rawCompile(); }, 300); } }); return {}; } else { return await rawCompile(); } }