]> Lady’s Gitweb - Pisces/commitdiff
Rename base64.js to binary.js; reorganize contents
authorLady <redacted>
Sun, 25 Sep 2022 07:00:10 +0000 (00:00 -0700)
committerLady <redacted>
Fri, 12 May 2023 03:56:48 +0000 (20:56 -0700)
`atob` and `btoa` now have the much clearer names `base64Binary` and
`base64String`; filename‐safe equivalents have been added.

The intention is to add the base16 and base32 formats from R·F·C 3548
as well, although that work is still forthcoming.

base64.test.js [deleted file]
binary.js [moved from base64.js with 77% similarity]
binary.test.js [new file with mode: 0755]
mod.js

diff --git a/base64.test.js b/base64.test.js
deleted file mode 100755 (executable)
index 84d9057..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-// ♓🌟 Piscēs ∷ base64.test.js
-// ====================================================================
-//
-// Copyright © 2020–2022 Lady [@ Lady’s Computer].
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
-
-import {
-  assert,
-  assertEquals,
-  assertStrictEquals,
-  assertThrows,
-  describe,
-  it,
-} from "./dev-deps.js";
-import { a2b, b2a, isBase64 } from "./base64.js";
-
-const base64s = {
-  "AGIAYQBzAGUANgA0": "base64",
-  "R/Q=": new Uint16Array([62535]),
-  "S0lCSQ==": new Uint8ClampedArray([75, 73, 66, 73]).buffer,
-  YmFzZTY0: new DataView(
-    new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
-  ),
-};
-
-describe("a2b", () => {
-  it("[[Call]] returns the correct data", () => {
-    assertEquals(
-      a2b("AGIAYQBzAGUANgA0"),
-      new Uint8Array(
-        Array.prototype.map.call(
-          "\u{0}b\u{0}a\u{0}s\u{0}e\u{0}6\u{0}4",
-          ($) => $.charCodeAt(0),
-        ),
-      ).buffer,
-      "AGIAYQBzAGUANgA0",
-    );
-    assertEquals(
-      a2b("R/Q="),
-      new Uint16Array([62535]).buffer,
-      "R/Q=",
-    );
-    assertEquals(
-      a2b("S0lCSQ=="),
-      new Uint8ClampedArray([75, 73, 66, 73]).buffer,
-      "S0lCSQ==",
-    );
-    assertEquals(
-      a2b("YmFzZTY0"),
-      new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
-      "YmFzZTY0",
-    );
-  });
-
-  it("[[Call]] throws when provided with an invalid character", () => {
-    assertThrows(() => a2b("abc_"));
-  });
-
-  it("[[Call]] throws when provided with an invalid equals", () => {
-    assertThrows(() => a2b("abc=="));
-  });
-
-  it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 4", () => {
-    assertThrows(() => a2b("abcde"));
-    assertThrows(() => a2b("abcde==="));
-  });
-});
-
-describe("b2a", () => {
-  it("[[Call]] returns the correct string", () => {
-    Object.entries(base64s).forEach(([key, value]) =>
-      assertStrictEquals(b2a(value), key)
-    );
-  });
-});
-
-describe("isBase64", () => {
-  it("[[Call]] returns true for base64 strings", () => {
-    Object.keys(base64s).forEach((key) => assert(isBase64(key)));
-  });
-
-  it("[[Call]] returns false for others", () => {
-    [
-      undefined,
-      null,
-      true,
-      Symbol(),
-      27,
-      98n,
-      {},
-      [],
-      () => {},
-      new Proxy({}, {}),
-      "abc_",
-      "a",
-      "abc==",
-    ].forEach((value) => assert(!isBase64(value)));
-  });
-});
similarity index 77%
rename from base64.js
rename to binary.js
index e9661b7577e2e7a7445a07098985f322c30f506b..dc4b6120ebfffb0e5ed845de73808b40c937b90a 100644 (file)
--- a/base64.js
+++ b/binary.js
@@ -1,4 +1,4 @@
-// ♓🌟 Piscēs ∷ base64.js
+// ♓🌟 Piscēs ∷ binary.js
 // ====================================================================
 //
 // Copyright © 2020–2022 Lady [@ Lady’s Computer].
@@ -45,7 +45,36 @@ const {
   setUint16: viewSetUint16,
 } = viewPrototype;
 
-const base64CodeUnitIterablePrototype = {
+const bufferFromArgs = ($, $s) =>
+  $ instanceof Buffer
+    ? $
+    : $ instanceof View
+    ? call(getViewBuffer, $, [])
+    : $ instanceof TypedArray
+    ? call(getTypedArrayBuffer, $, [])
+    : ((string) =>
+      call(
+        getViewBuffer,
+        reduce(
+          string,
+          (result, ucsCharacter, index) => (
+            call(viewSetUint16, result, [
+              index * 2,
+              getCodeUnit(ucsCharacter, 0),
+            ]), result
+          ),
+          new View(new Buffer(string.length * 2)),
+        ),
+        [],
+      ))(
+        typeof $ == "string"
+          ? $
+          : hasOwnProperty($, "raw")
+          ? rawString($, ...$s)
+          : `${$}`,
+      );
+
+const binaryCodeUnitIterablePrototype = {
   [iteratorSymbol]() {
     return {
       next: bind(
@@ -57,22 +86,7 @@ const base64CodeUnitIterablePrototype = {
   },
 };
 
-/**
- * Returns an ArrayBuffer generated from the provided Base64.
- *
- * This function can also be used as a tag for a template literal. The
- * literal will be interpreted akin to `String.raw`.
- */
-export const a2b = ($, ...$s) => {
-  const source = stringReplace(
-    typeof $ == "string"
-      ? $
-      : hasOwnProperty($, "raw")
-      ? rawString($, ...$s)
-      : `${$}`,
-    /[\t\n\f\r ]+/gu,
-    "",
-  );
+const decodeBase64 = (source, safe = false) => {
   const u6s = map(
     source.length % 4 == 0
       ? stringReplace(source, /={1,2}$/u, "")
@@ -85,9 +99,9 @@ export const a2b = ($, ...$s) => {
         ? code - 71
         : code >= 0x30 && code <= 0x39
         ? code + 4
-        : code == 0x2B
+        : code == (safe ? 0x2D : 0x2B)
         ? 62
-        : code == 0x2F
+        : code == (safe ? 0x5F : 0x2F)
         ? 63
         : -1;
       if (result < 0) {
@@ -124,47 +138,13 @@ export const a2b = ($, ...$s) => {
   return call(getViewBuffer, dataView, []);
 };
 
-/**
- * Returns a (big‐endian) base64 string created from a typed array,
- * buffer, or string.
- *
- * This function can also be used as a tag for a template literal. The
- * literal will be interpreted akin to `String.raw`.
- */
-export const b2a = ($, ...$s) => {
-  const buffer = $ instanceof Buffer
-    ? $
-    : $ instanceof View
-    ? call(getViewBuffer, $, [])
-    : $ instanceof TypedArray
-    ? call(getTypedArrayBuffer, $, [])
-    : ((string) =>
-      call(
-        getViewBuffer,
-        reduce(
-          string,
-          (result, ucsCharacter, index) => (
-            call(viewSetUint16, result, [
-              index * 2,
-              getCodeUnit(ucsCharacter, 0),
-            ]), result
-          ),
-          new View(new Buffer(string.length * 2)),
-        ),
-        [],
-      ))(
-        typeof $ == "string"
-          ? $
-          : hasOwnProperty($, "raw")
-          ? rawString($, ...$s)
-          : `${$}`,
-      );
+const encodeBase64 = (buffer, safe = false) => {
   const dataView = new View(buffer);
   const byteLength = call(getBufferByteLength, buffer, []);
   const minimumLengthOfResults = ceil(byteLength * 4 / 3);
   const resultingCodeUnits = fill(
     objectCreate(
-      base64CodeUnitIterablePrototype,
+      binaryCodeUnitIterablePrototype,
       {
         length: {
           value: minimumLengthOfResults +
@@ -200,9 +180,9 @@ export const b2a = ($, ...$s) => {
       : u6 < 62
       ? u6 - 4
       : u6 < 63
-      ? 43
+      ? (safe ? 0x2D : 0x2B)
       : u6 < 64
-      ? 47
+      ? (safe ? 0x5F : 0x2F)
       : -1;
     if (result < 0) {
       throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
@@ -213,6 +193,56 @@ export const b2a = ($, ...$s) => {
   return stringFromCodeUnits(...resultingCodeUnits);
 };
 
+const sourceFromArgs = ($, $s) =>
+  stringReplace(
+    typeof $ == "string"
+      ? $
+      : hasOwnProperty($, "raw")
+      ? rawString($, ...$s)
+      : `${$}`,
+    /[\t\n\f\r ]+/gu,
+    "",
+  );
+
+/**
+ * Returns an ArrayBuffer generated from the provided Base64.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base64Binary = ($, ...$s) =>
+  decodeBase64(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base64 string created from a typed array,
+ * buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base64String = ($, ...$s) =>
+  encodeBase64(bufferFromArgs($, $s));
+
+/**
+ * Returns an ArrayBuffer generated from the provided filename‐safe
+ * Base64.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const filenameSafeBase64Binary = ($, ...$s) =>
+  decodeBase64(sourceFromArgs($, $s), true);
+
+/**
+ * Returns a (big‐endian) filename‐safe base64 string created from a
+ * typed array, buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const filenameSafeBase64String = ($, ...$s) =>
+  encodeBase64(bufferFromArgs($, $s), true);
+
 /**
  * Returns whether the provided value is a Base64 string.
  *
@@ -230,3 +260,21 @@ export const isBase64 = ($) => {
       call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null;
   }
 };
+
+/**
+ * Returns whether the provided value is a filename‐safe base64 string.
+ *
+ * Returns false if the provided value is not a string primitive.
+ */
+export const isFilenameSafeBase64 = ($) => {
+  if (typeof $ !== "string") {
+    return false;
+  } else {
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    const trimmed = source.length % 4 == 0
+      ? stringReplace(source, /={1,2}$/u, "")
+      : source;
+    return trimmed.length % 4 != 1 &&
+      call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null;
+  }
+};
diff --git a/binary.test.js b/binary.test.js
new file mode 100755 (executable)
index 0000000..161f8db
--- /dev/null
@@ -0,0 +1,191 @@
+// ♓🌟 Piscēs ∷ binary.test.js
+// ====================================================================
+//
+// Copyright © 2020–2022 Lady [@ Lady’s Computer].
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import {
+  assert,
+  assertEquals,
+  assertStrictEquals,
+  assertThrows,
+  describe,
+  it,
+} from "./dev-deps.js";
+import {
+  base64Binary,
+  base64String,
+  filenameSafeBase64Binary,
+  filenameSafeBase64String,
+  isBase64,
+  isFilenameSafeBase64,
+} from "./binary.js";
+
+const base64s = {
+  "AGIAYQBzAGUANgA0": "base64",
+  "R/Q=": new Uint16Array([62535]),
+  "S0lCSQ==": new Uint8ClampedArray([75, 73, 66, 73]).buffer,
+  YmFzZTY0: new DataView(
+    new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
+  ),
+};
+
+describe("base64Binary", () => {
+  it("[[Call]] returns the correct data", () => {
+    assertEquals(
+      base64Binary("AGIAYQBzAGUANgA0"),
+      new Uint8Array(
+        Array.prototype.map.call(
+          "\u{0}b\u{0}a\u{0}s\u{0}e\u{0}6\u{0}4",
+          ($) => $.charCodeAt(0),
+        ),
+      ).buffer,
+      "AGIAYQBzAGUANgA0",
+    );
+    assertEquals(
+      base64Binary("R/Q="),
+      new Uint16Array([62535]).buffer,
+      "R/Q=",
+    );
+    assertEquals(
+      base64Binary("S0lCSQ=="),
+      new Uint8ClampedArray([75, 73, 66, 73]).buffer,
+      "S0lCSQ==",
+    );
+    assertEquals(
+      base64Binary("YmFzZTY0"),
+      new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
+      "YmFzZTY0",
+    );
+  });
+
+  it("[[Call]] throws when provided with an invalid character", () => {
+    assertThrows(() => base64Binary("abc_"));
+  });
+
+  it("[[Call]] throws when provided with an invalid equals", () => {
+    assertThrows(() => base64Binary("abc=="));
+  });
+
+  it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 4", () => {
+    assertThrows(() => base64Binary("abcde"));
+    assertThrows(() => base64Binary("abcde==="));
+  });
+});
+
+describe("base64String", () => {
+  it("[[Call]] returns the correct string", () => {
+    Object.entries(base64s).forEach(([key, value]) =>
+      assertStrictEquals(base64String(value), key)
+    );
+  });
+});
+
+describe("filenameSafeBase64Binary", () => {
+  it("[[Call]] returns the correct data", () => {
+    assertEquals(
+      filenameSafeBase64Binary("AGIAYQBzAGUANgA0"),
+      new Uint8Array(
+        Array.prototype.map.call(
+          "\u{0}b\u{0}a\u{0}s\u{0}e\u{0}6\u{0}4",
+          ($) => $.charCodeAt(0),
+        ),
+      ).buffer,
+      "AGIAYQBzAGUANgA0",
+    );
+    assertEquals(
+      filenameSafeBase64Binary("R_Q="),
+      new Uint16Array([62535]).buffer,
+      "R/Q=",
+    );
+    assertEquals(
+      filenameSafeBase64Binary("S0lCSQ=="),
+      new Uint8ClampedArray([75, 73, 66, 73]).buffer,
+      "S0lCSQ==",
+    );
+    assertEquals(
+      filenameSafeBase64Binary("YmFzZTY0"),
+      new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
+      "YmFzZTY0",
+    );
+  });
+
+  it("[[Call]] throws when provided with an invalid character", () => {
+    assertThrows(() => filenameSafeBase64Binary("abc/"));
+  });
+
+  it("[[Call]] throws when provided with an invalid equals", () => {
+    assertThrows(() => filenameSafeBase64Binary("abc=="));
+  });
+
+  it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 4", () => {
+    assertThrows(() => filenameSafeBase64Binary("abcde"));
+    assertThrows(() => filenameSafeBase64Binary("abcde==="));
+  });
+});
+
+describe("filenameSafeBase64String", () => {
+  it("[[Call]] returns the correct string", () => {
+    Object.entries(base64s).forEach(([key, value]) =>
+      assertStrictEquals(
+        filenameSafeBase64String(value),
+        key.replace("+", "-").replace("/", "_"),
+      )
+    );
+  });
+});
+
+describe("isBase64", () => {
+  it("[[Call]] returns true for base64 strings", () => {
+    Object.keys(base64s).forEach((key) => assert(isBase64(key)));
+  });
+
+  it("[[Call]] returns false for others", () => {
+    [
+      undefined,
+      null,
+      true,
+      Symbol(),
+      27,
+      98n,
+      {},
+      [],
+      () => {},
+      new Proxy({}, {}),
+      "abc_",
+      "a",
+      "abc==",
+    ].forEach((value) => assert(!isBase64(value)));
+  });
+});
+
+describe("isFilenameSafeBase64", () => {
+  it("[[Call]] returns true for filename‐safe base64 strings", () => {
+    Object.keys(base64s).forEach((key) =>
+      assert(
+        isFilenameSafeBase64(key.replace("+", "-").replace("/", "_")),
+      )
+    );
+  });
+
+  it("[[Call]] returns false for others", () => {
+    [
+      undefined,
+      null,
+      true,
+      Symbol(),
+      27,
+      98n,
+      {},
+      [],
+      () => {},
+      new Proxy({}, {}),
+      "abc/",
+      "a",
+      "abc==",
+    ].forEach((value) => assert(!isFilenameSafeBase64(value)));
+  });
+});
diff --git a/mod.js b/mod.js
index 42b421c76005de2ba850807e3e6e9518dc3ccef1..13840c95b1fb48e5c25085130d26d682e26f607b 100644 (file)
--- a/mod.js
+++ b/mod.js
@@ -7,7 +7,7 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
 
-export * from "./base64.js";
+export * from "./binary.js";
 export * from "./collection.js";
 export * from "./function.js";
 export * from "./iri.js";
This page took 0.07747 seconds and 4 git commands to generate.