MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

readline state management + promisify for child_process

+214 -10
+30
src/modules/child_process.c
··· 1328 1328 return promise; 1329 1329 } 1330 1330 1331 + static ant_value_t exec_promisified_call(ant_t *js, ant_value_t *args, int nargs) { 1332 + ant_value_t original = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 1333 + if (!is_callable(original)) return js_mkerr(js, "exec promisify target is not callable"); 1334 + 1335 + ant_value_t call_result = sv_vm_call( 1336 + js->vm, js, original, js_getthis(js), 1337 + args, nargs, NULL, false 1338 + ); 1339 + 1340 + if (vtype(call_result) == T_PROMISE) return call_result; 1341 + if (is_err(call_result) || js->thrown_exists) { 1342 + ant_value_t promise = js_mkpromise(js); 1343 + ant_value_t ex = js->thrown_exists ? js->thrown_value : call_result; 1344 + js->thrown_exists = false; 1345 + js->thrown_value = js_mkundef(); 1346 + js->thrown_stack = js_mkundef(); 1347 + js_reject_promise(js, promise, ex); 1348 + return promise; 1349 + } 1350 + 1351 + ant_value_t promise = js_mkpromise(js); 1352 + js_resolve_promise(js, promise, call_result); 1353 + return promise; 1354 + } 1355 + 1331 1356 static ant_value_t builtin_execFile(ant_t *js, ant_value_t *args, int nargs) { 1332 1357 ant_value_t argv = js_mkundef(); 1333 1358 ant_value_t options = js_mkundef(); ··· 1815 1840 ant_value_t exec_file_fn = js_heavy_mkfun(js, builtin_execFile, js_mkundef()); 1816 1841 child_process_init_constructor(js); 1817 1842 1843 + js_set_symbol(js, exec_fn, 1844 + "nodejs.util.promisify.custom", 1845 + js_heavy_mkfun(js, exec_promisified_call, exec_fn) 1846 + ); 1847 + 1818 1848 js_set_symbol(js, exec_file_fn, 1819 1849 "nodejs.util.promisify.custom", 1820 1850 js_heavy_mkfun(js, exec_file_promisified_call, exec_file_fn)
+33 -10
src/modules/readline.c
··· 58 58 ant_value_t completer; 59 59 ant_value_t js_obj; 60 60 char *prompt; 61 + char *active_prompt; 61 62 char *line_buffer; 62 63 int line_pos; 63 64 int line_len; ··· 93 94 static rl_interface_t *interfaces = NULL; 94 95 static ant_value_t g_rl_async_iter_proto = 0; 95 96 static ant_value_t g_rl_interface_proto = 0; 97 + 98 + static const char *rl_render_prompt(const rl_interface_t *iface) { 99 + if (!iface) return ""; 100 + return iface->active_prompt ? iface->active_prompt : iface->prompt; 101 + } 102 + 103 + static void rl_set_active_prompt(rl_interface_t *iface, const char *prompt) { 104 + if (!iface) return; 105 + free(iface->active_prompt); 106 + iface->active_prompt = prompt ? strdup(prompt) : NULL; 107 + } 108 + 109 + static void rl_clear_active_prompt(rl_interface_t *iface) { 110 + if (!iface) return; 111 + free(iface->active_prompt); 112 + iface->active_prompt = NULL; 113 + } 96 114 97 115 static void rl_history_init(rl_history_t *hist, int capacity) { 98 116 hist->capacity = capacity > 0 ? capacity : DEFAULT_HISTORY_SIZE; ··· 207 225 } 208 226 209 227 static void move_cursor_to_line_start(rl_interface_t *iface, int cols) { 210 - int prompt_len = (int)strlen(iface->prompt); 228 + int prompt_len = (int)strlen(rl_render_prompt(iface)); 211 229 int cursor_cols = prompt_len + iface->line_pos; 212 230 int cursor_row = cursor_cols / cols; 213 231 if (cursor_cols > 0 && cursor_cols % cols == 0) cursor_row--; ··· 222 240 223 241 static void clear_line_display(rl_interface_t *iface) { 224 242 int cols = get_terminal_cols(); 225 - int prompt_len = (int)strlen(iface->prompt); 243 + int prompt_len = (int)strlen(rl_render_prompt(iface)); 226 244 int line_cols = prompt_len + iface->line_len; 227 245 int current_rows = line_cols > 0 ? (line_cols - 1) / cols + 1 : 1; 228 246 int rows = iface->last_render_rows > current_rows ? iface->last_render_rows : current_rows; ··· 243 261 static void refresh_line(rl_interface_t *iface) { 244 262 char buf[MAX_LINE_LENGTH + 256]; 245 263 int cols = get_terminal_cols(); 264 + const char *prompt = rl_render_prompt(iface); 246 265 247 266 clear_line_display(iface); 248 - snprintf(buf, sizeof(buf), "%s%s", iface->prompt, iface->line_buffer); 267 + snprintf(buf, sizeof(buf), "%s%s", prompt, iface->line_buffer); 249 268 write_output(iface, buf); 250 269 251 - int prompt_len = (int)strlen(iface->prompt); 270 + int prompt_len = (int)strlen(prompt); 252 271 int end_cols = prompt_len + iface->line_len; 253 272 int end_row = end_cols > 0 ? end_cols / cols : 0; 254 273 int cursor_cols = prompt_len + iface->line_pos; ··· 311 330 312 331 if (iface->line_pos == iface->line_len) { 313 332 int cols = get_terminal_cols(); 314 - int prompt_len = (int)strlen(iface->prompt); 333 + int prompt_len = (int)strlen(rl_render_prompt(iface)); 315 334 int total_cols = prompt_len + iface->line_len; 316 335 int rows = total_cols > 0 ? total_cols / cols + 1 : 1; 317 336 if (rows > iface->last_render_rows) iface->last_render_rows = rows; ··· 476 495 477 496 rl_history_add(&iface->history, line, iface->remove_history_duplicates); 478 497 emit_history_event(js, iface); 498 + rl_clear_active_prompt(iface); 479 499 480 500 ant_value_t line_val = js_mkstr(js, line, strlen(line)); 481 501 emit_event(js, iface, "line", &line_val, 1); ··· 662 682 iface->line_len = 0; 663 683 } 664 684 665 - write_output(iface, iface->prompt); 685 + write_output(iface, rl_render_prompt(iface)); 666 686 if (iface->line_len > 0) { 667 687 write_output(iface, iface->line_buffer); 668 688 } 669 689 670 690 start_reading(iface); 671 - 672 691 return js_mkundef(); 673 692 } 674 693 ··· 824 843 return js_mkerr(js, "callback must be a function"); 825 844 } 826 845 827 - write_output(iface, query); 846 + rl_set_active_prompt(iface, query); 847 + write_output(iface, rl_render_prompt(iface)); 828 848 if (!rl_add_listener(js, iface, "line", args[1], true)) return js_mkerr(js, "listener must be a function"); 829 849 830 850 return js_mkundef(); ··· 843 863 844 864 ant_value_t promise = js_mkpromise(js); 845 865 846 - write_output(iface, query); 866 + rl_set_active_prompt(iface, query); 867 + write_output(iface, rl_render_prompt(iface)); 847 868 848 869 iface->pending_question_resolve = js_get(js, promise, "_resolve"); 849 870 iface->pending_question_reject = js_get(js, promise, "_reject"); ··· 862 883 return result; 863 884 } 864 885 865 - int prompt_len = (int)strlen(iface->prompt); 886 + int prompt_len = (int)strlen(rl_render_prompt(iface)); 866 887 int total_cols = prompt_len + iface->line_pos; 867 888 868 889 int cols = 80; ··· 901 922 HASH_DEL(interfaces, iface); 902 923 903 924 free(iface->prompt); 925 + free(iface->active_prompt); 904 926 free(iface->line_buffer); 905 927 rl_history_free(&iface->history); 906 928 free(iface); ··· 1202 1224 1203 1225 iface->id = next_interface_id++; 1204 1226 iface->prompt = strdup(DEFAULT_PROMPT); 1227 + iface->active_prompt = NULL; 1205 1228 iface->line_buffer = calloc(MAX_LINE_LENGTH, 1); 1206 1229 iface->line_pos = 0; 1207 1230 iface->line_len = 0;
+11
tests/fixtures/readline_question_prompt_child.cjs
··· 1 + const readline = require('node:readline'); 2 + 3 + const rl = readline.createInterface({ 4 + input: process.stdin, 5 + output: process.stdout, 6 + }); 7 + 8 + rl.question('Q: ', answer => { 9 + console.log(`ANSWER ${JSON.stringify(answer)}`); 10 + rl.close(); 11 + });
+34
tests/test_child_process_exec_promisify.cjs
··· 1 + const { promisify } = require('util'); 2 + const { exec } = require('child_process'); 3 + 4 + async function main() { 5 + if (typeof promisify.custom !== 'symbol') { 6 + throw new Error('expected util.promisify.custom to be a symbol'); 7 + } 8 + 9 + if (typeof exec[promisify.custom] !== 'function') { 10 + throw new Error('expected exec to define util.promisify.custom'); 11 + } 12 + 13 + const execAsync = promisify(exec); 14 + const result = await execAsync('printf "OUT"; printf "ERR" >&2'); 15 + 16 + if (!result || typeof result !== 'object' || Array.isArray(result)) { 17 + throw new Error(`expected object result, got ${Object.prototype.toString.call(result)}`); 18 + } 19 + 20 + if (result.stdout !== 'OUT') { 21 + throw new Error(`expected stdout to be OUT, got ${JSON.stringify(result.stdout)}`); 22 + } 23 + 24 + if (result.stderr !== 'ERR') { 25 + throw new Error(`expected stderr to be ERR, got ${JSON.stringify(result.stderr)}`); 26 + } 27 + 28 + console.log('util.promisify(exec) resolves { stdout, stderr }'); 29 + } 30 + 31 + main().catch((err) => { 32 + console.error(err && err.stack ? err.stack : String(err)); 33 + process.exit(1); 34 + });
+106
tests/test_readline_question_prompt.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const path = require('path'); 3 + 4 + function fail(message) { 5 + throw new Error(message); 6 + } 7 + 8 + function runInPty() { 9 + const helper = path.join(__dirname, 'fixtures', 'readline_question_prompt_child.cjs'); 10 + const script = ` 11 + import os, select, signal, sys, time 12 + 13 + exec_path, helper = sys.argv[1], sys.argv[2] 14 + pid, master = os.forkpty() 15 + 16 + if pid == 0: 17 + os.execv(exec_path, [exec_path, helper]) 18 + 19 + buf = bytearray() 20 + sent = False 21 + exit_code = None 22 + deadline = time.time() + 7.0 23 + 24 + while time.time() < deadline: 25 + done, status = os.waitpid(pid, os.WNOHANG) 26 + if done == pid: 27 + exit_code = os.waitstatus_to_exitcode(status) 28 + break 29 + 30 + r, _, _ = select.select([master], [], [], 0.1) 31 + if master not in r: 32 + continue 33 + 34 + try: 35 + chunk = os.read(master, 4096) 36 + except OSError: 37 + break 38 + 39 + if not chunk: 40 + break 41 + 42 + buf.extend(chunk) 43 + if (not sent) and b'Q: ' in buf: 44 + os.write(master, b'ab\\x7fc\\r') 45 + sent = True 46 + 47 + if exit_code is None: 48 + os.kill(pid, signal.SIGKILL) 49 + _, status = os.waitpid(pid, 0) 50 + exit_code = os.waitstatus_to_exitcode(status) 51 + 52 + while True: 53 + r, _, _ = select.select([master], [], [], 0.05) 54 + if master not in r: 55 + break 56 + try: 57 + chunk = os.read(master, 4096) 58 + except OSError: 59 + break 60 + if not chunk: 61 + break 62 + buf.extend(chunk) 63 + 64 + sys.stdout.buffer.write(bytes(buf)) 65 + sys.exit(exit_code) 66 + `; 67 + 68 + if (process.platform === 'win32') { 69 + console.log('skipping readline question prompt tty test on win32'); 70 + process.exit(0); 71 + } 72 + 73 + return spawnSync('python3', ['-c', script, process.execPath, helper], { 74 + encoding: 'utf8', 75 + timeout: 9000, 76 + }); 77 + } 78 + 79 + const result = runInPty(); 80 + 81 + if (result.error && result.error.code === 'ENOENT') { 82 + console.log('skipping readline question prompt tty test because `python3` is unavailable'); 83 + process.exit(0); 84 + } 85 + 86 + if (result.error) throw result.error; 87 + 88 + const output = `${result.stdout || ''}${result.stderr || ''}`; 89 + 90 + if (result.status !== 0) { 91 + fail(`child exited ${result.status}\n${output}`); 92 + } 93 + 94 + if (!output.includes('Q: ')) { 95 + fail(`expected question prompt in output\n${output}`); 96 + } 97 + 98 + if (!output.includes('ANSWER "ac"')) { 99 + fail(`expected edited answer to round-trip\n${output}`); 100 + } 101 + 102 + if (output.includes('> ')) { 103 + fail(`default interface prompt leaked into question redraw\n${output}`); 104 + } 105 + 106 + console.log('readline question keeps the active prompt during redraw');