diff --git a/src/base.ts b/src/base.ts index 44e4987..1c4d3b7 100644 --- a/src/base.ts +++ b/src/base.ts @@ -1,6 +1,7 @@ import type { NumberSystem } from "."; import * as util from "./util"; +/** Creates a new number system object with the given radix/base. */ export function base(radix: number): NumberSystem { const rdx = Math.floor(Math.min(36, Math.max(2, radix))); const max = (255).toString(rdx).length; @@ -17,7 +18,7 @@ export function base(radix: number): NumberSystem { const charMatch = data.toString().toLowerCase().match(this.regex); if (charMatch == null) return ""; - const decoded = Buffer.from(util.padStart(charMatch, max).map(x => parseInt(x, rdx))); + const decoded = Buffer.from(util.padStart((data.toString().toLowerCase().match(this.regex) ?? []).join(""), max).map(x => parseInt(x, rdx))); return encoding ? decoded.toString(encoding) : decoded; } } diff --git a/src/base32.ts b/src/base32.ts index dd9dd8e..da80b33 100644 --- a/src/base32.ts +++ b/src/base32.ts @@ -4,10 +4,11 @@ export const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; export const radix = 32; export const regex = /[A-Z2-7]/gi; -const bits = util.quickMap(5, x => 2 ** (x * 8)).reverse(); -const chars = util.quickMap(8, x => 2 ** (x * 5)).reverse(); -const pads = util.quickMap(5, x => (8 - Math.ceil(x * 8 / 5)) % 8); +const bits = util.quickMap(5, x => 2 ** (x * 8), true); // 4294967296, 16777216, 65536, 256, 1 +const chars = util.quickMap(8, x => 2 ** (x * 5), true); // 34359738368, 1073741824, 33554432, 1048576, 32768, 1024, 32, 1 +const pads = util.quickMap(5, x => (8 - Math.ceil(x * 8 / 5)) % 8, false); // 0, 6, 4, 3, 1 +// Encodes the given data into a Base 32 encoded string. export function encode(data: string | Buffer, encoding: BufferEncoding = "utf8") { let padding = 0; return Buffer.from(data.toString(encoding), encoding).reduce((a, x, i, r) => { @@ -22,8 +23,9 @@ export function encode(data: string | Buffer, encoding: BufferEncoding = "utf8") charset[Math.floor(x / y) % 32]).join("")).join("") + "=".repeat(padding); } +// Decodes the given Base 32 encoded string into a buffer, or a string if a character encoding is provided. export function decode(data: string, encoding?: BufferEncoding) { - const decoded = Buffer.from(util.padEnd(data.toString().toUpperCase().match(regex) ?? [], 8) + const decoded = Buffer.from(util.padEnd((data.toString().toUpperCase().match(regex) ?? []).join(""), 8) .flatMap(x => { const value = util.stringToNumber(x, charset); return bits.slice(0, pads.findIndex(y => y == (x.match(/=/g) ?? []).length) || 5) diff --git a/src/base64.ts b/src/base64.ts index 53d4994..807bddc 100644 --- a/src/base64.ts +++ b/src/base64.ts @@ -4,10 +4,11 @@ export const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123 export const radix = 64; export const regex = /[A-Za-z0-9+/]/gi; -const bits = util.quickMap(3, x => 2 ** (x * 8)).reverse(); -const chars = util.quickMap(4, x => 2 ** (x * 6)).reverse(); -const pads = util.quickMap(3, x => (4 - Math.ceil(x * 4 / 3)) % 4); +const bits = util.quickMap(3, x => 2 ** (x * 8), true); // 65536, 256, 1 +const chars = util.quickMap(4, x => 2 ** (x * 6), true); // 262144, 4096, 64, 1 +const pads = util.quickMap(3, x => (4 - Math.ceil(x * 4 / 3)) % 4, false); // 0, 2, 1 +// Encodes the given data into a Base 64 encoded string. export function encode(data: string | Buffer, encoding: BufferEncoding = "utf8") { let padding = 0; return Buffer.from(data.toString(encoding), encoding).reduce((a, x, i, r) => { @@ -22,8 +23,9 @@ export function encode(data: string | Buffer, encoding: BufferEncoding = "utf8") charset[Math.floor(x / y) % 64]).join("")).join("") + "=".repeat(padding); } +// Decodes the given Base 64 encoded string into a buffer, or a string if a character encoding is provided. export function decode(data: string, encoding?: BufferEncoding) { - const decoded = Buffer.from(util.padEnd(data.toString().replaceAll("-", "+").replaceAll("_", "/").match(regex) ?? [], 4) + const decoded = Buffer.from(util.padEnd((data.toString().replaceAll("-", "+").replaceAll("_", "/").match(regex) ?? []).join(""), 4) .flatMap(x => { const value = util.stringToNumber(x, charset); return bits.slice(0, pads.findIndex(y => y == (x.match(/=/g) ?? []).length) || 3) diff --git a/src/cli.ts b/src/cli.ts index 06d7af3..938707e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,9 +10,10 @@ type Options = { out: string; } +// Get the version from the package.json file const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")); +// The main CLI function export = function (argv: string[], mode: "encode" | "decode") { - if (!["encode", "decode"].includes(mode)) return process.exit(1); const isEncode = mode.startsWith("e"); const descStart = `${mode[0].toUpperCase() + mode.slice(1)}s the specified input data ${isEncode ? "with" : "from"}`; @@ -61,6 +62,7 @@ export = function (argv: string[], mode: "encode" | "decode") { .alias("radix") .action(actionHandler); + // Create a command for each base/radix in the dencodeme object for (const [command, base] of Object.entries(dencodeme) .filter((x): x is [string, NumberSystem] => typeof x[1] !== "function") .map((x): [string, number] => [x[0], x[1].radix])) { @@ -76,5 +78,6 @@ export = function (argv: string[], mode: "encode" | "decode") { .action(actionHandler.bind(null, command as Exclude)); } + // Parse the given arguments and run the program program.parse(argv, { from: "user" }); } diff --git a/src/index.ts b/src/index.ts index 2482eb8..8d376ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,20 +2,33 @@ import { base } from "./base"; import * as b32 from "./base32"; import * as b64 from "./base64"; +/** The number system object interface. */ export interface NumberSystem { + /** The character set of the number system. */ readonly charset: string; + /** The radix/base of the number system. */ readonly radix: number; + /** The regex used to match characters in the number system. */ readonly regex: RegExp; + /** Encodes the given data into an encoded string. */ encode(data: string | Buffer, encoding?: BufferEncoding): string; + /** Decodes the given encoded string into a buffer, or a string if a character encoding is provided. */ decode(data: string, encoding?: BufferEncoding): string | Buffer; } export * from "./base"; +/** The binary (Base 2) number system object. */ export const binary = base(2); +/** The octal (Base 8) number system object. */ export const octal = base(8); +/** The decimal (Base 10) number system object. */ export const decimal = base(10); +/** The hexadecimal (Base 16) number system object. */ export const hexadecimal = base(16); +/** The Base 32 number system object. */ export const base32: NumberSystem = b32; +/** The Base 36 number system object. */ export const base36 = base(36); +/** The Base 64 number system object. */ export const base64: NumberSystem = b64; diff --git a/src/util.ts b/src/util.ts index 35a1a93..e8adbe6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,4 @@ +// Returns a mapper function to split a string into an array of strings of the given length. function getMapper(length: number) { return (a: string[], x: string, i: number) => { if (i % length == 0) a.push(x); @@ -6,18 +7,22 @@ function getMapper(length: number) { } } -export function padStart(data: string[], length: number): string[] { +// Pads the given string with zeros to the left, and splits it into an array of strings of the given length. +export function padStart(data: string, length: number): string[] { return [ ...Array((length - data.length % length) % length).fill("0"), ...data ].reduce(getMapper(length), []); } -export function padEnd(data: string[], length: number): string[] { +// Pads the given string with equals signs to the right, and splits it into an array of strings of the given length. +export function padEnd(data: string, length: number): string[] { return [ ...data, ...Array((length - data.length % length) % length).fill("=") ].reduce(getMapper(length), []); } -export function quickMap(length: number, multiplier: (x: number) => number) { - return Array(length).fill(0).map((_, x) => multiplier(x)); +// Creates an array of numbers from 0 to length - 1, and multiplies each number by the given multiplier. +export function quickMap(length: number, multiplier: (x: number) => number, reverse: boolean = false) { + return Array(length).fill(0).map((_, x) => multiplier(reverse ? -x + length - 1 : x)); } +// Converts the given string to a number using the given character set. export function stringToNumber(str: string, charset: string) { return str.split("").reverse().map(x => charset.indexOf(x)).reduce((a, x, i) => a + (x >= 0 ? x * charset.length ** i : 0), 0); }