|
2 | 2 | // This module is browser compatible. |
3 | 3 |
|
4 | 4 | /** |
5 | | - * Utilities for encoding and decoding to and from hex in a streaming manner. |
| 5 | + * TransformStream classes to encode and decode to and from hexadecimal data in a streaming manner. |
6 | 6 | * |
7 | 7 | * ```ts |
8 | 8 | * import { assertEquals } from "@std/assert"; |
9 | | - * import { HexDecoderStream } from "@std/encoding/unstable-hex-stream"; |
10 | | - * import { toText } from "@std/streams/to-text"; |
| 9 | + * import { encodeHex } from "@std/encoding/unstable-hex"; |
| 10 | + * import { HexEncoderStream } from "@std/encoding/unstable-hex-stream"; |
| 11 | + * import { toText } from "@std/streams"; |
11 | 12 | * |
12 | | - * const stream = ReadableStream.from(["48656c6c6f2c", "20776f726c6421"]) |
13 | | - * .pipeThrough(new HexDecoderStream()) |
14 | | - * .pipeThrough(new TextDecoderStream()); |
| 13 | + * const readable = (await Deno.open("./deno.lock")) |
| 14 | + * .readable |
| 15 | + * .pipeThrough(new HexEncoderStream({ output: "string" })); |
15 | 16 | * |
16 | | - * assertEquals(await toText(stream), "Hello, world!"); |
| 17 | + * assertEquals( |
| 18 | + * await toText(readable), |
| 19 | + * encodeHex(await Deno.readFile("./deno.lock")), |
| 20 | + * ); |
17 | 21 | * ``` |
18 | 22 | * |
19 | 23 | * @experimental **UNSTABLE**: New API, yet to be vetted. |
20 | 24 | * |
21 | 25 | * @module |
22 | 26 | */ |
23 | 27 |
|
24 | | -import { decodeHex, encodeHex } from "./hex.ts"; |
25 | 28 | import type { Uint8Array_ } from "./_types.ts"; |
26 | 29 | export type { Uint8Array_ }; |
| 30 | +import { |
| 31 | + calcMax, |
| 32 | + decodeRawHex as decode, |
| 33 | + encodeRawHex as encode, |
| 34 | +} from "./unstable_hex.ts"; |
| 35 | +import { detach } from "./_common_detach.ts"; |
| 36 | + |
| 37 | +type Expect<T> = T extends "bytes" ? Uint8Array_ : string; |
27 | 38 |
|
28 | 39 | /** |
29 | | - * Converts a Uint8Array stream into a hex-encoded stream. |
| 40 | + * Transforms a {@linkcode Uint8Array<ArrayBuffer>} stream into a hexadecimal stream. |
30 | 41 | * |
31 | 42 | * @experimental **UNSTABLE**: New API, yet to be vetted. |
32 | 43 | * |
33 | | - * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-8} |
| 44 | + * @typeParam T The type of the hexadecimal stream. |
34 | 45 | * |
35 | | - * @example Usage |
| 46 | + * @example Basic Usage |
36 | 47 | * ```ts |
37 | 48 | * import { assertEquals } from "@std/assert"; |
38 | | - * import { encodeHex } from "@std/encoding/hex"; |
| 49 | + * import { encodeHex } from "@std/encoding/unstable-hex"; |
39 | 50 | * import { HexEncoderStream } from "@std/encoding/unstable-hex-stream"; |
40 | | - * import { toText } from "@std/streams/to-text"; |
| 51 | + * import { toText } from "@std/streams"; |
41 | 52 | * |
42 | | - * const stream = ReadableStream.from(["Hello,", " world!"]) |
43 | | - * .pipeThrough(new TextEncoderStream()) |
44 | | - * .pipeThrough(new HexEncoderStream()); |
| 53 | + * const readable = (await Deno.open("./deno.lock")) |
| 54 | + * .readable |
| 55 | + * .pipeThrough(new HexEncoderStream({ output: "string" })); |
45 | 56 | * |
46 | | - * assertEquals(await toText(stream), encodeHex(new TextEncoder().encode("Hello, world!"))); |
| 57 | + * assertEquals( |
| 58 | + * await toText(readable), |
| 59 | + * encodeHex(await Deno.readFile("./deno.lock")), |
| 60 | + * ); |
47 | 61 | * ``` |
48 | 62 | */ |
49 | | -export class HexEncoderStream extends TransformStream<Uint8Array, string> { |
50 | | - constructor() { |
| 63 | +export class HexEncoderStream<T extends "string" | "bytes"> |
| 64 | + extends TransformStream< |
| 65 | + Uint8Array_, |
| 66 | + T extends "bytes" ? Uint8Array_ : string |
| 67 | + > { |
| 68 | + /** |
| 69 | + * Constructs a new instance. |
| 70 | + * |
| 71 | + * @param options The options for the hexadecimal stream. |
| 72 | + */ |
| 73 | + constructor(options: { output?: T } = {}) { |
| 74 | + const decode = function (): (input: Uint8Array_) => Expect<T> { |
| 75 | + if (options.output === "bytes") return (x) => x as Expect<T>; |
| 76 | + const decoder = new TextDecoder(); |
| 77 | + return (x) => decoder.decode(x) as Expect<T>; |
| 78 | + }(); |
51 | 79 | super({ |
52 | 80 | transform(chunk, controller) { |
53 | | - controller.enqueue(encodeHex(chunk)); |
| 81 | + const [output, i] = detach(chunk, calcMax(chunk.length)); |
| 82 | + encode(output, i, 0); |
| 83 | + controller.enqueue(decode(output)); |
54 | 84 | }, |
55 | 85 | }); |
56 | 86 | } |
57 | 87 | } |
58 | 88 |
|
59 | 89 | /** |
60 | | - * Decodes a hex-encoded stream into a Uint8Array stream. |
| 90 | + * Transforms a hexadecimal stream into a {@link Uint8Array<ArrayBuffer>} stream. |
61 | 91 | * |
62 | 92 | * @experimental **UNSTABLE**: New API, yet to be vetted. |
63 | 93 | * |
64 | | - * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-8} |
| 94 | + * @typeParam T The type of the hexadecimal stream. |
65 | 95 | * |
66 | | - * @example Usage |
| 96 | + * @example Basic Usage |
67 | 97 | * ```ts |
68 | 98 | * import { assertEquals } from "@std/assert"; |
69 | | - * import { HexDecoderStream } from "@std/encoding/unstable-hex-stream"; |
70 | | - * import { toText } from "@std/streams/to-text"; |
| 99 | + * import { |
| 100 | + * HexDecoderStream, |
| 101 | + * HexEncoderStream, |
| 102 | + * } from "@std/encoding/unstable-hex-stream"; |
| 103 | + * import { toBytes } from "@std/streams/unstable-to-bytes"; |
71 | 104 | * |
72 | | - * const stream = ReadableStream.from(["48656c6c6f2c", "20776f726c6421"]) |
73 | | - * .pipeThrough(new HexDecoderStream()) |
74 | | - * .pipeThrough(new TextDecoderStream()); |
| 105 | + * const readable = (await Deno.open("./deno.lock")) |
| 106 | + * .readable |
| 107 | + * .pipeThrough(new HexEncoderStream({ output: "bytes" })) |
| 108 | + * .pipeThrough(new HexDecoderStream({ input: "bytes" })); |
75 | 109 | * |
76 | | - * assertEquals(await toText(stream), "Hello, world!"); |
| 110 | + * assertEquals( |
| 111 | + * await toBytes(readable), |
| 112 | + * await Deno.readFile("./deno.lock"), |
| 113 | + * ); |
77 | 114 | * ``` |
78 | 115 | */ |
79 | | -export class HexDecoderStream extends TransformStream<string, Uint8Array_> { |
80 | | - constructor() { |
81 | | - let push = ""; |
| 116 | +export class HexDecoderStream<T extends "string" | "bytes"> |
| 117 | + extends TransformStream< |
| 118 | + T extends "bytes" ? Uint8Array_ : string, |
| 119 | + Uint8Array_ |
| 120 | + > { |
| 121 | + /** |
| 122 | + * Constructs a new instance. |
| 123 | + * |
| 124 | + * @param options The options of the hexadecimal stream. |
| 125 | + */ |
| 126 | + constructor(options: { input?: T } = {}) { |
| 127 | + const encode = function (): (input: Expect<T>) => Uint8Array_ { |
| 128 | + if (options.input === "bytes") return (x) => x as Uint8Array_; |
| 129 | + const encoder = new TextEncoder(); |
| 130 | + return (x) => encoder.encode(x as string) as Uint8Array_; |
| 131 | + }(); |
| 132 | + const push = new Uint8Array(1); |
| 133 | + let remainder = 0; |
82 | 134 | super({ |
83 | 135 | transform(chunk, controller) { |
84 | | - push += chunk; |
85 | | - if (push.length < 2) { |
86 | | - return; |
| 136 | + let output = encode(chunk); |
| 137 | + if (remainder) { |
| 138 | + output = detach(output, remainder + output.length)[0]; |
| 139 | + output.set(push.subarray(0, remainder)); |
87 | 140 | } |
88 | | - const remainder = -push.length % 2; |
89 | | - controller.enqueue(decodeHex(push.slice(0, remainder || undefined))); |
90 | | - push = remainder ? push.slice(remainder) : ""; |
| 141 | + remainder = output.length % 2; |
| 142 | + if (remainder) push.set(output.subarray(-remainder)); |
| 143 | + const o = decode(output.subarray(0, -remainder || undefined), 0, 0); |
| 144 | + controller.enqueue(output.subarray(0, o)); |
91 | 145 | }, |
92 | 146 | flush(controller) { |
93 | | - if (push.length) { |
94 | | - controller.enqueue(decodeHex(push)); |
| 147 | + if (remainder) { |
| 148 | + const o = decode(push.subarray(0, remainder), 0, 0); |
| 149 | + controller.enqueue(push.subarray(0, o)); |
95 | 150 | } |
96 | 151 | }, |
97 | 152 | }); |
|
0 commit comments