From 09ee9a828009ab1fbe59f611da64f48f9e9e573b Mon Sep 17 00:00:00 2001 From: Nikolai Vavilov Date: Sat, 6 Jun 2020 22:56:49 +0300 Subject: [PATCH] feat(std/node): Buffer (#5925) --- std/node/_fs/_fs_common.ts | 5 + std/node/_fs/_fs_readFile.ts | 18 +- std/node/_fs/_fs_writeFile.ts | 5 +- std/node/buffer.ts | 417 ++++++++++++++++++++++++++++++++++ std/node/module.ts | 2 + 5 files changed, 436 insertions(+), 11 deletions(-) create mode 100644 std/node/buffer.ts diff --git a/std/node/_fs/_fs_common.ts b/std/node/_fs/_fs_common.ts index 1f00bc4814..bf7c0f6755 100644 --- a/std/node/_fs/_fs_common.ts +++ b/std/node/_fs/_fs_common.ts @@ -35,6 +35,11 @@ export function getEncoding( const encoding = typeof optOrCallback === "string" ? optOrCallback : optOrCallback.encoding; if (!encoding) return null; + return encoding; +} + +export function checkEncoding(encoding: string | null): string | null { + if (!encoding) return null; if (encoding === "utf8" || encoding === "utf-8") { return "utf8"; } diff --git a/std/node/_fs/_fs_readFile.ts b/std/node/_fs/_fs_readFile.ts index ccea71cd67..448045fd24 100644 --- a/std/node/_fs/_fs_readFile.ts +++ b/std/node/_fs/_fs_readFile.ts @@ -3,23 +3,23 @@ import { intoCallbackAPIWithIntercept, MaybeEmpty } from "../_utils.ts"; import { getEncoding, FileOptions } from "./_fs_common.ts"; +import { Buffer } from "../buffer.ts"; import { fromFileUrl } from "../path.ts"; const { readFile: denoReadFile, readFileSync: denoReadFileSync } = Deno; type ReadFileCallback = ( err: MaybeEmpty, - data: MaybeEmpty + data: MaybeEmpty ) => void; function maybeDecode( data: Uint8Array, encoding: string | null -): string | Uint8Array { - if (encoding === "utf8") { - return new TextDecoder().decode(data); - } - return data; +): string | Buffer { + const buffer = new Buffer(data.buffer, data.byteOffset, data.byteLength); + if (encoding) return buffer.toString(encoding); + return buffer; } export function readFile( @@ -37,9 +37,9 @@ export function readFile( const encoding = getEncoding(optOrCallback); - intoCallbackAPIWithIntercept( + intoCallbackAPIWithIntercept( denoReadFile, - (data: Uint8Array): string | Uint8Array => maybeDecode(data, encoding), + (data: Uint8Array): string | Buffer => maybeDecode(data, encoding), cb, path ); @@ -48,7 +48,7 @@ export function readFile( export function readFileSync( path: string | URL, opt?: FileOptions | string -): string | Uint8Array { +): string | Buffer { path = path instanceof URL ? fromFileUrl(path) : path; return maybeDecode(denoReadFileSync(path), getEncoding(opt)); } diff --git a/std/node/_fs/_fs_writeFile.ts b/std/node/_fs/_fs_writeFile.ts index 72e75c97a6..4ede426383 100644 --- a/std/node/_fs/_fs_writeFile.ts +++ b/std/node/_fs/_fs_writeFile.ts @@ -7,6 +7,7 @@ import { CallbackWithError, isFileOptions, getEncoding, + checkEncoding, getOpenOptions, } from "./_fs_common.ts"; @@ -35,7 +36,7 @@ export function writeFile( ? options.mode : undefined; - const encoding = getEncoding(options) || "utf8"; + const encoding = checkEncoding(getEncoding(options)) || "utf8"; const openOptions = getOpenOptions(flag || "w"); if (typeof data === "string" && encoding === "utf8") @@ -82,7 +83,7 @@ export function writeFileSync( ? options.mode : undefined; - const encoding = getEncoding(options) || "utf8"; + const encoding = checkEncoding(getEncoding(options)) || "utf8"; const openOptions = getOpenOptions(flag || "w"); if (typeof data === "string" && encoding === "utf8") diff --git a/std/node/buffer.ts b/std/node/buffer.ts new file mode 100644 index 0000000000..fa911ee6f7 --- /dev/null +++ b/std/node/buffer.ts @@ -0,0 +1,417 @@ +/** + * See also https://nodejs.org/api/buffer.html + */ +export default class Buffer extends Uint8Array { + /** + * Allocates a new Buffer of size bytes. + */ + static alloc(size: number): Buffer { + return new Buffer(size); + } + + /** + * Returns the byte length of a string when encoded. This is not the same as + * String.prototype.length, which does not account for the encoding that is + * used to convert the string into bytes. + */ + static byteLength( + string: string | Buffer | ArrayBufferView | ArrayBuffer | SharedArrayBuffer + ): number { + if (typeof string != "string") return string.byteLength; + return new TextEncoder().encode(string).length; + } + + /** + * Returns a new Buffer which is the result of concatenating all the Buffer + * instances in the list together. + */ + static concat(list: Buffer[] | Uint8Array[], totalLength?: number): Buffer { + if (totalLength == undefined) { + totalLength = 0; + for (const buf of list) { + totalLength += buf.length; + } + } + + const buffer = new Buffer(totalLength); + let pos = 0; + for (const buf of list) { + buffer.set(buf, pos); + pos += buf.length; + } + + return buffer; + } + + /** + * Allocates a new Buffer using an array of bytes in the range 0 – 255. Array + * entries outside that range will be truncated to fit into it. + */ + static from(array: number[]): Buffer; + /** + * This creates a view of the ArrayBuffer without copying the underlying + * memory. For example, when passed a reference to the .buffer property of a + * TypedArray instance, the newly created Buffer will share the same allocated + * memory as the TypedArray. + */ + static from( + arrayBuffer: ArrayBuffer | SharedArrayBuffer, + byteOffset?: number, + length?: number + ): Buffer; + /** + * Copies the passed buffer data onto a new Buffer instance. + */ + static from(buffer: Buffer | Uint8Array): Buffer; + /** + * Creates a new Buffer containing string. + */ + static from(string: string): Buffer; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static from(value: any, offset?: number, length?: number): Buffer { + if (typeof value == "string") + return new Buffer(new TextEncoder().encode(value).buffer); + + // workaround for https://github.com/microsoft/TypeScript/issues/38446 + return new Buffer(value, offset!, length); + } + + /** + * Returns true if obj is a Buffer, false otherwise. + */ + static isBuffer(obj: object): obj is Buffer { + return obj instanceof Buffer; + } + + /** + * Copies data from a region of buf to a region in target, even if the target + * memory region overlaps with buf. + */ + copy( + targetBuffer: Buffer | Uint8Array, + targetStart = 0, + sourceStart = 0, + sourceEnd = this.length + ): number { + const sourceBuffer = this.subarray(sourceStart, sourceEnd); + targetBuffer.set(sourceBuffer, targetStart); + return sourceBuffer.length; + } + + readBigInt64BE(offset = 0): bigint { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getBigInt64(offset); + } + readBigInt64LE(offset = 0): bigint { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getBigInt64(offset, true); + } + + readBigUInt64BE(offset = 0): bigint { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getBigUint64(offset); + } + readBigUInt64LE(offset = 0): bigint { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getBigUint64(offset, true); + } + + readDoubleBE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getFloat64(offset); + } + readDoubleLE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getFloat64(offset, true); + } + + readFloatBE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getFloat32(offset); + } + readFloatLE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getFloat32(offset, true); + } + + readInt8(offset = 0): number { + return new DataView(this.buffer, this.byteOffset, this.byteLength).getInt8( + offset + ); + } + + readInt16BE(offset = 0): number { + return new DataView(this.buffer, this.byteOffset, this.byteLength).getInt16( + offset + ); + } + readInt16LE(offset = 0): number { + return new DataView(this.buffer, this.byteOffset, this.byteLength).getInt16( + offset, + true + ); + } + + readInt32BE(offset = 0): number { + return new DataView(this.buffer, this.byteOffset, this.byteLength).getInt32( + offset + ); + } + readInt32LE(offset = 0): number { + return new DataView(this.buffer, this.byteOffset, this.byteLength).getInt32( + offset, + true + ); + } + + readUInt8(offset = 0): number { + return new DataView(this.buffer, this.byteOffset, this.byteLength).getUint8( + offset + ); + } + + readUInt16BE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getUint16(offset); + } + readUInt16LE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getUint16(offset, true); + } + + readUInt32BE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getUint32(offset); + } + readUInt32LE(offset = 0): number { + return new DataView( + this.buffer, + this.byteOffset, + this.byteLength + ).getUint32(offset, true); + } + + /** + * Returns a new Buffer that references the same memory as the original, but + * offset and cropped by the start and end indices. + */ + slice(begin = 0, end = this.length): Buffer { + // workaround for https://github.com/microsoft/TypeScript/issues/38665 + return this.subarray(begin, end) as Buffer; + } + + /** + * Returns a JSON representation of buf. JSON.stringify() implicitly calls + * this function when stringifying a Buffer instance. + */ + toJSON(): object { + return { type: "Buffer", data: Array.from(this) }; + } + + /** + * Decodes buf to a string according to the specified character encoding in + * encoding. start and end may be passed to decode only a subset of buf. + */ + toString(encoding = "utf8", start = 0, end = this.length): string { + return new TextDecoder(encoding).decode(this.subarray(start, end)); + } + + /** + * Writes string to buf at offset according to the character encoding in + * encoding. The length parameter is the number of bytes to write. If buf did + * not contain enough space to fit the entire string, only part of string will + * be written. However, partially encoded characters will not be written. + */ + write(string: string, offset = 0, length = this.length): number { + return new TextEncoder().encodeInto( + string, + this.subarray(offset, offset + length) + ).written; + } + + writeBigInt64BE(value: bigint, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setBigInt64( + offset, + value + ); + return offset + 4; + } + writeBigInt64LE(value: bigint, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setBigInt64( + offset, + value, + true + ); + return offset + 4; + } + + writeBigUInt64BE(value: bigint, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setBigUint64( + offset, + value + ); + return offset + 4; + } + writeBigUInt64LE(value: bigint, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setBigUint64( + offset, + value, + true + ); + return offset + 4; + } + + writeDoubleBE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setFloat64( + offset, + value + ); + return offset + 8; + } + writeDoubleLE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setFloat64( + offset, + value, + true + ); + return offset + 8; + } + + writeFloatBE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setFloat32( + offset, + value + ); + return offset + 4; + } + writeFloatLE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setFloat32( + offset, + value, + true + ); + return offset + 4; + } + + writeInt8(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setInt8( + offset, + value + ); + return offset + 1; + } + + writeInt16BE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setInt16( + offset, + value + ); + return offset + 2; + } + writeInt16LE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setInt16( + offset, + value, + true + ); + return offset + 2; + } + + writeInt32BE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setUint32( + offset, + value + ); + return offset + 4; + } + writeInt32LE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setInt32( + offset, + value, + true + ); + return offset + 4; + } + + writeUInt8(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setUint8( + offset, + value + ); + return offset + 1; + } + + writeUInt16BE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setUint16( + offset, + value + ); + return offset + 2; + } + writeUInt16LE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setUint16( + offset, + value, + true + ); + return offset + 2; + } + + writeUInt32BE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setUint32( + offset, + value + ); + return offset + 4; + } + writeUInt32LE(value: number, offset = 0): number { + new DataView(this.buffer, this.byteOffset, this.byteLength).setUint32( + offset, + value, + true + ); + return offset + 4; + } +} + +export { Buffer }; + +Object.defineProperty(globalThis, "Buffer", { + value: Buffer, + enumerable: false, + writable: true, + configurable: true, +}); diff --git a/std/node/module.ts b/std/node/module.ts index 13b2ac325e..8d0160f78a 100644 --- a/std/node/module.ts +++ b/std/node/module.ts @@ -21,6 +21,7 @@ import "./global.ts"; +import * as nodeBuffer from "./buffer.ts"; import * as nodeFS from "./fs.ts"; import * as nodeUtil from "./util.ts"; import * as nodePath from "./path.ts"; @@ -595,6 +596,7 @@ function createNativeModule(id: string, exports: any): Module { return mod; } +nativeModulePolyfill.set("buffer", createNativeModule("buffer", nodeBuffer)); nativeModulePolyfill.set("fs", createNativeModule("fs", nodeFS)); nativeModulePolyfill.set("events", createNativeModule("events", nodeEvents)); nativeModulePolyfill.set("os", createNativeModule("os", nodeOs));