-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
130 lines (113 loc) · 3.84 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env node
const /*util =*/ { promisify } = require("node:util");
const exec = promisify(require("node:child_process").exec);
const fs = require("node:fs/promises");
// elm doesn't accept windows-style paths, so path.join does not work here
// const path = require("node:path");
// const elmPathJoin = path.join;
const elmPathJoin = (left, right) =>
left.replace(/\/+$/g, "") + "/" + right.replace(/^\/+/g, "");
const ELM_BOILERPLATE = "module Main exposing (main)\n\nimport Interact\n\n-- imported code\n";
// TODO: quote for directories with special characters
const MY_BASE_DIRECTORY = __dirname;
const COMPILED_SRC = elmPathJoin(MY_BASE_DIRECTORY, "compiled.js");
const MAIN_SRC = elmPathJoin(MY_BASE_DIRECTORY, "src/Main.elm");
// TODO: allow ability to supply these as CLI args
const ELM_COMPILE_FLAGS = "--optimize";
const showError = (message, ...args) => {
if(args.length === 0) {
console.error("[elm-line]", message);
}
else {
console.error("[elm-line]", message + ":");
console.group();
console.error(...args);
console.groupEnd();
}
};
// TODO: allow multiple instances of elm-line running at the same time?
// (use separate, explicitly temporary directories)
const cmd = `cd ${MY_BASE_DIRECTORY} && elm make --output ${COMPILED_SRC} ${MAIN_SRC} ${ELM_COMPILE_FLAGS}`;
const main = async function(args) {
// TODO: filter out command line variables
let [ filePath ] = args;
if(!filePath) {
showError(`No file name given`);
process.exit(2);
}
// TODO: error check this
let content = await fs.readFile(filePath);
content = ELM_BOILERPLATE + content;
await fs.writeFile(MAIN_SRC, content);
let stderr;
try {
// we only care about this command for its side effects
// WARNING: potential side effect, debugging information will be
// treated as fatal.
// TODO: procure exit code to ensure process completed correctly?
let data = await exec(cmd);
stderr = data.stderr;
}
catch(exception) {
stderr = exception.stderr;
}
if(stderr) {
showError("Error while building", stderr);
process.exit(1);
}
const { Elm } = require(COMPILED_SRC);
const main = Elm.Main.init();
// handle inputs
const inputBuffer = [];
let elmState = {
evaluating: false,
}
// data path: this program -> elm program
const addInput = (input) => {
inputBuffer.push(input);
if(!elmState.evaluating) {
sendInputToElm();
}
};
const sendInputToElm = () => {
elmState.evaluating = true;
let line = inputBuffer.shift();
main.ports.get.send(line);
};
// data path: elm program -> this program
const onElmData = (data) => {
// forward the elm program's output
console.log(data);
writePromptHead();
elmState.evaluating = false;
if(inputBuffer.length > 0) {
// continue parsing inputs
sendInputToElm();
}
};
main.ports.put.subscribe(onElmData);
const readline = require('readline');
const writePromptHead = () => {
// stdin.isTTY seems to be unset when stdout.isTTY is unset
// either behavior is acceptable for this function
if(process.stdin.isTTY/* && process.stdout.isTTY*/) {
process.stdout.write("> ");
}
};
writePromptHead();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on("line", (line) => {
// send the data line we received
addInput(line);
});
// TODO: allow the Elm program to close STDIN?
// TODO: signal to Elm that input has been closed?
rl.once("close", () => {
// end of input
});
};
main(process.argv.slice(2));