diff options
author | 2023-01-09 18:27:56 -0600 | |
---|---|---|
committer | 2023-01-09 16:27:56 -0800 | |
commit | 4ef60da8a9ceffa4c28811bbb62d946d4e667e24 (patch) | |
tree | f4205c142c76a1034f3bcdd10881de7f6436a693 | |
parent | 0e7f69f179176a51a75d00836224d208f20e1c49 (diff) | |
download | bun-4ef60da8a9ceffa4c28811bbb62d946d4e667e24.tar.gz bun-4ef60da8a9ceffa4c28811bbb62d946d4e667e24.tar.zst bun-4ef60da8a9ceffa4c28811bbb62d946d4e667e24.zip |
refactor(readline/promises): re-export readline.promises from readline (#1748)
* refactor(readline/promises): re-export readline.promises from readline/promises
* fix(readline): don't export Readline from `readline`
* perf(readline): return Promise.reject immediately after failed validation
-rw-r--r-- | src/bun.js/readline.exports.js | 208 | ||||
-rw-r--r-- | src/bun.js/readline_promises.exports.js | 214 |
2 files changed, 183 insertions, 239 deletions
diff --git a/src/bun.js/readline.exports.js b/src/bun.js/readline.exports.js index 73b8d24c5..d5f6da1cd 100644 --- a/src/bun.js/readline.exports.js +++ b/src/bun.js/readline.exports.js @@ -28,6 +28,7 @@ var { Array, RegExp, String, Bun } = import.meta.primordials; var EventEmitter = import.meta.require("node:events"); var { clearTimeout, setTimeout } = import.meta.require("timers"); var { StringDecoder } = import.meta.require("string_decoder"); +var isWritable; var { inspect } = Bun; var debug = process.env.BUN_JS_DEBUG ? console.log : () => {}; @@ -372,6 +373,14 @@ class ERR_USE_AFTER_CLOSE extends NodeError { } } +class AbortError extends Error { + code; + constructor() { + super("The operation was aborted"); + this.code = "ABORT_ERR"; + } +} + // Validators /** @@ -2696,18 +2705,19 @@ Interface.prototype.question = function question(query, options, cb) { options = kEmptyObject; } - if (options.signal) { - validateAbortSignal(options.signal, "options.signal"); - if (options.signal.aborted) { + var signal = options?.signal; + if (signal) { + validateAbortSignal(signal, "options.signal"); + if (signal.aborted) { return; } var onAbort = () => { this[kQuestionCancel](); }; - options.signal.addEventListener("abort", onAbort, { once: true }); + signal.addEventListener("abort", onAbort, { once: true }); var cleanup = () => { - options.signal.removeEventListener("abort", onAbort); + signal.removeEventListener("abort", onAbort); }; var originalCb = cb; cb = @@ -2723,6 +2733,7 @@ Interface.prototype.question = function question(query, options, cb) { this[kQuestion](query, cb); } }; + Interface.prototype.question[promisify.custom] = function question( query, options, @@ -2731,26 +2742,24 @@ Interface.prototype.question[promisify.custom] = function question( options = kEmptyObject; } - if (options.signal && options.signal.aborted) { - return PromiseReject( - new AbortError(undefined, { cause: options.signal.reason }), - ); + var signal = options?.signal; + + if (signal && signal.aborted) { + return PromiseReject(new AbortError(undefined, { cause: signal.reason })); } return new Promise((resolve, reject) => { var cb = resolve; - - if (options.signal) { + if (signal) { var onAbort = () => { - reject(new AbortError(undefined, { cause: options.signal.reason })); + reject(new AbortError(undefined, { cause: signal.reason })); }; - options.signal.addEventListener("abort", onAbort, { once: true }); + signal.addEventListener("abort", onAbort, { once: true }); cb = (answer) => { - options.signal.removeEventListener("abort", onAbort); + signal.removeEventListener("abort", onAbort); resolve(answer); }; } - this.question(query, options, cb); }); }; @@ -3081,6 +3090,157 @@ function _ttyWriteDumb(s, key) { } } +class Readline { + #autoCommit = false; + #stream; + #todo = []; + + constructor(stream, options = undefined) { + isWritable ??= import.meta.require("node:stream").isWritable; + if (!isWritable(stream)) + throw new ERR_INVALID_ARG_TYPE("stream", "Writable", stream); + this.#stream = stream; + if (options?.autoCommit != null) { + validateBoolean(options.autoCommit, "options.autoCommit"); + this.#autoCommit = options.autoCommit; + } + } + + /** + * Moves the cursor to the x and y coordinate on the given stream. + * @param {integer} x + * @param {integer} [y] + * @returns {Readline} this + */ + cursorTo(x, y = undefined) { + validateInteger(x, "x"); + if (y != null) validateInteger(y, "y"); + + var data = y == null ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush.call(this.#todo, data); + + return this; + } + + /** + * Moves the cursor relative to its current location. + * @param {integer} dx + * @param {integer} dy + * @returns {Readline} this + */ + moveCursor(dx, dy) { + if (dx || dy) { + validateInteger(dx, "dx"); + validateInteger(dy, "dy"); + + var data = ""; + + if (dx < 0) { + data += CSI`${-dx}D`; + } else if (dx > 0) { + data += CSI`${dx}C`; + } + + if (dy < 0) { + data += CSI`${-dy}A`; + } else if (dy > 0) { + data += CSI`${dy}B`; + } + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush.call(this.#todo, data); + } + return this; + } + + /** + * Clears the current line the cursor is on. + * @param {-1|0|1} dir Direction to clear: + * -1 for left of the cursor + * +1 for right of the cursor + * 0 for the entire line + * @returns {Readline} this + */ + clearLine(dir) { + validateInteger(dir, "dir", -1, 1); + + var data = + dir < 0 ? kClearToLineBeginning : dir > 0 ? kClearToLineEnd : kClearLine; + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush.call(this.#todo, data); + return this; + } + + /** + * Clears the screen from the current position of the cursor down. + * @returns {Readline} this + */ + clearScreenDown() { + if (this.#autoCommit) { + process.nextTick(() => this.#stream.write(kClearScreenDown)); + } else { + ArrayPrototypePush.call(this.#todo, kClearScreenDown); + } + return this; + } + + /** + * Sends all the pending actions to the associated `stream` and clears the + * internal list of pending actions. + * @returns {Promise<void>} Resolves when all pending actions have been + * flushed to the associated `stream`. + */ + commit() { + return new Promise((resolve) => { + this.#stream.write(ArrayPrototypeJoin.call(this.#todo, ""), resolve); + this.#todo = []; + }); + } + + /** + * Clears the internal list of pending actions without sending it to the + * associated `stream`. + * @returns {Readline} this + */ + rollback() { + this.#todo = []; + return this; + } +} + +var PromisesInterface = class Interface extends _Interface { + // eslint-disable-next-line no-useless-constructor + constructor(input, output, completer, terminal) { + super(input, output, completer, terminal); + } + question(query, options = kEmptyObject) { + var signal = options?.signal; + if (signal) { + validateAbortSignal(signal, "options.signal"); + if (signal.aborted) { + return PromiseReject( + new AbortError(undefined, { cause: signal.reason }), + ); + } + } + return new Promise((resolve, reject) => { + var cb = resolve; + if (options?.signal) { + var onAbort = () => { + this[kQuestionCancel](); + reject(new AbortError(undefined, { cause: signal.reason })); + }; + signal.addEventListener("abort", onAbort, { once: true }); + cb = (answer) => { + signal.removeEventListener("abort", onAbort); + resolve(answer); + }; + } + this[kQuestion](query, cb); + }); + } +}; + // ---------------------------------------------------------------------------- // Exports // ---------------------------------------------------------------------------- @@ -3092,7 +3252,11 @@ export var cursorTo = cursorTo; export var emitKeypressEvents = emitKeypressEvents; export var moveCursor = moveCursor; export var promises = { - [SymbolFor("__UNIMPLEMENTED__")]: true, + Readline, + Interface: PromisesInterface, + createInterface(input, output, completer, terminal) { + return new Interface(input, output, completer, terminal); + }, }; export default { @@ -3107,22 +3271,10 @@ export default { [SymbolFor("__BUN_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__")]: { CSI, - _Interface, utils: { getStringWidth, stripVTControlCharacters, }, - shared: { - kEmptyObject, - validateBoolean, - validateInteger, - validateAbortSignal, - ERR_INVALID_ARG_TYPE, - }, - symbols: { - kQuestion, - kQuestionCancel, - }, }, [SymbolFor("CommonJS")]: 0, }; diff --git a/src/bun.js/readline_promises.exports.js b/src/bun.js/readline_promises.exports.js index 615b69ec7..b3cd52584 100644 --- a/src/bun.js/readline_promises.exports.js +++ b/src/bun.js/readline_promises.exports.js @@ -1,218 +1,10 @@ -// Attribution: Some parts of of this module are derived from code originating from the Node.js -// readline module which is licensed under an MIT license: -// -// Copyright Node.js contributors. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -var { Promise } = import.meta.primordials; -var readline = import.meta.require("node:readline"); -var isWritable; - -var ArrayPrototypePush = Array.prototype.push; -var ArrayPrototypeJoin = Array.prototype.join; -var SymbolFor = Symbol.for; -var kInternal = SymbolFor("__BUN_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__"); - var { - CSI, - _Interface, - symbols: { kQuestion, kQuestionCancel }, - shared: { - kEmptyObject, - validateAbortSignal, - validateBoolean, - validateInteger, - ERR_INVALID_ARG_TYPE, - }, -} = readline[kInternal]; - -var { kClearToLineBeginning, kClearToLineEnd, kClearLine, kClearScreenDown } = - CSI; - -class AbortError extends Error { - code; - constructor() { - super("The operation was aborted"); - this.code = "ABORT_ERR"; - } -} - -export class Readline { - #autoCommit = false; - #stream; - #todo = []; - - constructor(stream, options = undefined) { - isWritable ??= import.meta.require("node:stream").isWritable; - if (!isWritable(stream)) - throw new ERR_INVALID_ARG_TYPE("stream", "Writable", stream); - this.#stream = stream; - if (options?.autoCommit != null) { - validateBoolean(options.autoCommit, "options.autoCommit"); - this.#autoCommit = options.autoCommit; - } - } - - /** - * Moves the cursor to the x and y coordinate on the given stream. - * @param {integer} x - * @param {integer} [y] - * @returns {Readline} this - */ - cursorTo(x, y = undefined) { - validateInteger(x, "x"); - if (y != null) validateInteger(y, "y"); - - var data = y == null ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; - if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); - else ArrayPrototypePush.call(this.#todo, data); - - return this; - } - - /** - * Moves the cursor relative to its current location. - * @param {integer} dx - * @param {integer} dy - * @returns {Readline} this - */ - moveCursor(dx, dy) { - if (dx || dy) { - validateInteger(dx, "dx"); - validateInteger(dy, "dy"); - - var data = ""; - - if (dx < 0) { - data += CSI`${-dx}D`; - } else if (dx > 0) { - data += CSI`${dx}C`; - } - - if (dy < 0) { - data += CSI`${-dy}A`; - } else if (dy > 0) { - data += CSI`${dy}B`; - } - if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); - else ArrayPrototypePush.call(this.#todo, data); - } - return this; - } - - /** - * Clears the current line the cursor is on. - * @param {-1|0|1} dir Direction to clear: - * -1 for left of the cursor - * +1 for right of the cursor - * 0 for the entire line - * @returns {Readline} this - */ - clearLine(dir) { - validateInteger(dir, "dir", -1, 1); - - var data = - dir < 0 ? kClearToLineBeginning : dir > 0 ? kClearToLineEnd : kClearLine; - if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); - else ArrayPrototypePush.call(this.#todo, data); - return this; - } - - /** - * Clears the screen from the current position of the cursor down. - * @returns {Readline} this - */ - clearScreenDown() { - if (this.#autoCommit) { - process.nextTick(() => this.#stream.write(kClearScreenDown)); - } else { - ArrayPrototypePush.call(this.#todo, kClearScreenDown); - } - return this; - } - - /** - * Sends all the pending actions to the associated `stream` and clears the - * internal list of pending actions. - * @returns {Promise<void>} Resolves when all pending actions have been - * flushed to the associated `stream`. - */ - commit() { - return new Promise((resolve) => { - this.#stream.write(ArrayPrototypeJoin.call(this.#todo, ""), resolve); - this.#todo = []; - }); - } - - /** - * Clears the internal list of pending actions without sending it to the - * associated `stream`. - * @returns {Readline} this - */ - rollback() { - this.#todo = []; - return this; - } -} - -export class Interface extends _Interface { - // eslint-disable-next-line no-useless-constructor - constructor(input, output, completer, terminal) { - super(input, output, completer, terminal); - } - question(query, options = kEmptyObject) { - return new Promise((resolve, reject) => { - var cb = resolve; - - if (options?.signal) { - validateAbortSignal(options.signal, "options.signal"); - if (options.signal.aborted) { - return reject( - new AbortError(undefined, { cause: options.signal.reason }), - ); - } - - var onAbort = () => { - this[kQuestionCancel](); - reject(new AbortError(undefined, { cause: options.signal.reason })); - }; - options.signal.addEventListener("abort", onAbort, { once: true }); - cb = (answer) => { - options.signal.removeEventListener("abort", onAbort); - resolve(answer); - }; - } - - this[kQuestion](query, cb); - }); - } -} - -export function createInterface(input, output, completer, terminal) { - return new Interface(input, output, completer, terminal); -} + promises: { Readline, Interface, createInterface }, +} = import.meta.require("node:readline"); export default { Readline, Interface, createInterface, - - [SymbolFor("CommonJS")]: 0, + [Symbol.for("CommonJS")]: 0, }; |