mirror of
https://github.com/imjasonh/deno-image
synced 2026-07-07 00:33:45 +00:00
remove vendor and .gitignore
Signed-off-by: Carlos Santana <csantana23@gmail.com>
This commit is contained in:
parent
d9993f6b98
commit
18da0fca18
14 changed files with 1 additions and 2424 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
example
|
||||
vendor/**
|
||||
|
|
|
|||
|
|
@ -1,703 +0,0 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
// This module is browser compatible. Do not rely on good formatting of values
|
||||
// for AssertionError messages in browsers.
|
||||
|
||||
import {
|
||||
bgGreen,
|
||||
bgRed,
|
||||
bold,
|
||||
gray,
|
||||
green,
|
||||
red,
|
||||
stripColor,
|
||||
white,
|
||||
} from "../fmt/colors.ts";
|
||||
import { diff, DiffResult, diffstr, DiffType } from "./_diff.ts";
|
||||
|
||||
const CAN_NOT_DISPLAY = "[Cannot display]";
|
||||
|
||||
interface Constructor {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
new (...args: any[]): any;
|
||||
}
|
||||
|
||||
export class AssertionError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "AssertionError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input into a string. Objects, Sets and Maps are sorted so as to
|
||||
* make tests less flaky
|
||||
* @param v Value to be formatted
|
||||
*/
|
||||
export function _format(v: unknown): string {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const { Deno } = globalThis as any;
|
||||
return typeof Deno?.inspect === "function"
|
||||
? Deno.inspect(v, {
|
||||
depth: Infinity,
|
||||
sorted: true,
|
||||
trailingComma: true,
|
||||
compact: false,
|
||||
iterableLimit: Infinity,
|
||||
})
|
||||
: `"${String(v).replace(/(?=["\\])/g, "\\")}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Colors the output of assertion diffs
|
||||
* @param diffType Difference type, either added or removed
|
||||
*/
|
||||
function createColor(
|
||||
diffType: DiffType,
|
||||
{ background = false } = {},
|
||||
): (s: string) => string {
|
||||
switch (diffType) {
|
||||
case DiffType.added:
|
||||
return (s: string): string =>
|
||||
background ? bgGreen(white(s)) : green(bold(s));
|
||||
case DiffType.removed:
|
||||
return (s: string): string => background ? bgRed(white(s)) : red(bold(s));
|
||||
default:
|
||||
return white;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes `+` or `-` in diff output
|
||||
* @param diffType Difference type, either added or removed
|
||||
*/
|
||||
function createSign(diffType: DiffType): string {
|
||||
switch (diffType) {
|
||||
case DiffType.added:
|
||||
return "+ ";
|
||||
case DiffType.removed:
|
||||
return "- ";
|
||||
default:
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessage(
|
||||
diffResult: ReadonlyArray<DiffResult<string>>,
|
||||
{ stringDiff = false } = {},
|
||||
): string[] {
|
||||
const messages: string[] = [], diffMessages: string[] = [];
|
||||
messages.push("");
|
||||
messages.push("");
|
||||
messages.push(
|
||||
` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${
|
||||
green(bold("Expected"))
|
||||
}`,
|
||||
);
|
||||
messages.push("");
|
||||
messages.push("");
|
||||
diffResult.forEach((result: DiffResult<string>): void => {
|
||||
const c = createColor(result.type);
|
||||
const line = result.details?.map((detail) =>
|
||||
detail.type !== DiffType.common
|
||||
? createColor(detail.type, { background: true })(detail.value)
|
||||
: detail.value
|
||||
).join("") ?? result.value;
|
||||
diffMessages.push(c(`${createSign(result.type)}${line}`));
|
||||
});
|
||||
messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages));
|
||||
messages.push("");
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
function isKeyedCollection(x: unknown): x is Set<unknown> {
|
||||
return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep equality comparison used in assertions
|
||||
* @param c actual value
|
||||
* @param d expected value
|
||||
*/
|
||||
export function equal(c: unknown, d: unknown): boolean {
|
||||
const seen = new Map();
|
||||
return (function compare(a: unknown, b: unknown): boolean {
|
||||
// Have to render RegExp & Date for string comparison
|
||||
// unless it's mistreated as object
|
||||
if (
|
||||
a &&
|
||||
b &&
|
||||
((a instanceof RegExp && b instanceof RegExp) ||
|
||||
(a instanceof URL && b instanceof URL))
|
||||
) {
|
||||
return String(a) === String(b);
|
||||
}
|
||||
if (a instanceof Date && b instanceof Date) {
|
||||
const aTime = a.getTime();
|
||||
const bTime = b.getTime();
|
||||
// Check for NaN equality manually since NaN is not
|
||||
// equal to itself.
|
||||
if (Number.isNaN(aTime) && Number.isNaN(bTime)) {
|
||||
return true;
|
||||
}
|
||||
return a.getTime() === b.getTime();
|
||||
}
|
||||
if (Object.is(a, b)) {
|
||||
return true;
|
||||
}
|
||||
if (a && typeof a === "object" && b && typeof b === "object") {
|
||||
if (a && b && !constructorsEqual(a, b)) {
|
||||
return false;
|
||||
}
|
||||
if (a instanceof WeakMap || b instanceof WeakMap) {
|
||||
if (!(a instanceof WeakMap && b instanceof WeakMap)) return false;
|
||||
throw new TypeError("cannot compare WeakMap instances");
|
||||
}
|
||||
if (a instanceof WeakSet || b instanceof WeakSet) {
|
||||
if (!(a instanceof WeakSet && b instanceof WeakSet)) return false;
|
||||
throw new TypeError("cannot compare WeakSet instances");
|
||||
}
|
||||
if (seen.get(a) === b) {
|
||||
return true;
|
||||
}
|
||||
if (Object.keys(a || {}).length !== Object.keys(b || {}).length) {
|
||||
return false;
|
||||
}
|
||||
if (isKeyedCollection(a) && isKeyedCollection(b)) {
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let unmatchedEntries = a.size;
|
||||
|
||||
for (const [aKey, aValue] of a.entries()) {
|
||||
for (const [bKey, bValue] of b.entries()) {
|
||||
/* Given that Map keys can be references, we need
|
||||
* to ensure that they are also deeply equal */
|
||||
if (
|
||||
(aKey === aValue && bKey === bValue && compare(aKey, bKey)) ||
|
||||
(compare(aKey, bKey) && compare(aValue, bValue))
|
||||
) {
|
||||
unmatchedEntries--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unmatchedEntries === 0;
|
||||
}
|
||||
const merged = { ...a, ...b };
|
||||
for (
|
||||
const key of [
|
||||
...Object.getOwnPropertyNames(merged),
|
||||
...Object.getOwnPropertySymbols(merged),
|
||||
]
|
||||
) {
|
||||
type Key = keyof typeof merged;
|
||||
if (!compare(a && a[key as Key], b && b[key as Key])) {
|
||||
return false;
|
||||
}
|
||||
if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
seen.set(a, b);
|
||||
if (a instanceof WeakRef || b instanceof WeakRef) {
|
||||
if (!(a instanceof WeakRef && b instanceof WeakRef)) return false;
|
||||
return compare(a.deref(), b.deref());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})(c, d);
|
||||
}
|
||||
|
||||
// deno-lint-ignore ban-types
|
||||
function constructorsEqual(a: object, b: object) {
|
||||
return a.constructor === b.constructor ||
|
||||
a.constructor === Object && !b.constructor ||
|
||||
!a.constructor && b.constructor === Object;
|
||||
}
|
||||
|
||||
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
|
||||
export function assert(expr: unknown, msg = ""): asserts expr {
|
||||
if (!expr) {
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` and `expected` are equal, deeply. If not
|
||||
* deeply equal, then throw.
|
||||
*
|
||||
* Type parameter can be specified to ensure values under comparison have the same type.
|
||||
* For example:
|
||||
* ```ts
|
||||
* import { assertEquals } from "./asserts.ts";
|
||||
*
|
||||
* assertEquals<number>(1, 2)
|
||||
* ```
|
||||
*/
|
||||
export function assertEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertEquals<T>(actual: T, expected: T, msg?: string): void;
|
||||
export function assertEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (equal(actual, expected)) {
|
||||
return;
|
||||
}
|
||||
let message = "";
|
||||
const actualString = _format(actual);
|
||||
const expectedString = _format(expected);
|
||||
try {
|
||||
const stringDiff = (typeof actual === "string") &&
|
||||
(typeof expected === "string");
|
||||
const diffResult = stringDiff
|
||||
? diffstr(actual as string, expected as string)
|
||||
: diff(actualString.split("\n"), expectedString.split("\n"));
|
||||
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
|
||||
message = `Values are not equal:\n${diffMsg}`;
|
||||
} catch {
|
||||
message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`;
|
||||
}
|
||||
if (msg) {
|
||||
message = msg;
|
||||
}
|
||||
throw new AssertionError(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` and `expected` are not equal, deeply.
|
||||
* If not then throw.
|
||||
*
|
||||
* Type parameter can be specified to ensure values under comparison have the same type.
|
||||
* For example:
|
||||
* ```ts
|
||||
* import { assertNotEquals } from "./asserts.ts";
|
||||
*
|
||||
* assertNotEquals<number>(1, 2)
|
||||
* ```
|
||||
*/
|
||||
export function assertNotEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertNotEquals<T>(actual: T, expected: T, msg?: string): void;
|
||||
export function assertNotEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (!equal(actual, expected)) {
|
||||
return;
|
||||
}
|
||||
let actualString: string;
|
||||
let expectedString: string;
|
||||
try {
|
||||
actualString = String(actual);
|
||||
} catch {
|
||||
actualString = "[Cannot display]";
|
||||
}
|
||||
try {
|
||||
expectedString = String(expected);
|
||||
} catch {
|
||||
expectedString = "[Cannot display]";
|
||||
}
|
||||
if (!msg) {
|
||||
msg = `actual: ${actualString} expected: ${expectedString}`;
|
||||
}
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` and `expected` are strictly equal. If
|
||||
* not then throw.
|
||||
*
|
||||
* ```ts
|
||||
* import { assertStrictEquals } from "./asserts.ts";
|
||||
*
|
||||
* assertStrictEquals(1, 2)
|
||||
* ```
|
||||
*/
|
||||
export function assertStrictEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertStrictEquals<T>(
|
||||
actual: T,
|
||||
expected: T,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertStrictEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (actual === expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message: string;
|
||||
|
||||
if (msg) {
|
||||
message = msg;
|
||||
} else {
|
||||
const actualString = _format(actual);
|
||||
const expectedString = _format(expected);
|
||||
|
||||
if (actualString === expectedString) {
|
||||
const withOffset = actualString
|
||||
.split("\n")
|
||||
.map((l) => ` ${l}`)
|
||||
.join("\n");
|
||||
message =
|
||||
`Values have the same structure but are not reference-equal:\n\n${
|
||||
red(withOffset)
|
||||
}\n`;
|
||||
} else {
|
||||
try {
|
||||
const stringDiff = (typeof actual === "string") &&
|
||||
(typeof expected === "string");
|
||||
const diffResult = stringDiff
|
||||
? diffstr(actual as string, expected as string)
|
||||
: diff(actualString.split("\n"), expectedString.split("\n"));
|
||||
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
|
||||
message = `Values are not strictly equal:\n${diffMsg}`;
|
||||
} catch {
|
||||
message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` and `expected` are not strictly equal.
|
||||
* If the values are strictly equal then throw.
|
||||
*
|
||||
* ```ts
|
||||
* import { assertNotStrictEquals } from "./asserts.ts";
|
||||
*
|
||||
* assertNotStrictEquals(1, 1)
|
||||
* ```
|
||||
*/
|
||||
export function assertNotStrictEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertNotStrictEquals<T>(
|
||||
actual: T,
|
||||
expected: T,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertNotStrictEquals(
|
||||
actual: unknown,
|
||||
expected: unknown,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (actual !== expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AssertionError(
|
||||
msg ?? `Expected "actual" to be strictly unequal to: ${_format(actual)}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that actual is not null or undefined.
|
||||
* If not then throw.
|
||||
*/
|
||||
export function assertExists<T>(
|
||||
actual: T,
|
||||
msg?: string,
|
||||
): asserts actual is NonNullable<T> {
|
||||
if (actual === undefined || actual === null) {
|
||||
if (!msg) {
|
||||
msg = `actual: "${actual}" expected to not be null or undefined`;
|
||||
}
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that actual includes expected. If not
|
||||
* then throw.
|
||||
*/
|
||||
export function assertStringIncludes(
|
||||
actual: string,
|
||||
expected: string,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (!actual.includes(expected)) {
|
||||
if (!msg) {
|
||||
msg = `actual: "${actual}" expected to contain: "${expected}"`;
|
||||
}
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` includes the `expected` values.
|
||||
* If not then an error will be thrown.
|
||||
*
|
||||
* Type parameter can be specified to ensure values under comparison have the same type.
|
||||
* For example:
|
||||
*
|
||||
* ```ts
|
||||
* import { assertArrayIncludes } from "./asserts.ts";
|
||||
*
|
||||
* assertArrayIncludes<number>([1, 2], [2])
|
||||
* ```
|
||||
*/
|
||||
export function assertArrayIncludes(
|
||||
actual: ArrayLike<unknown>,
|
||||
expected: ArrayLike<unknown>,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertArrayIncludes<T>(
|
||||
actual: ArrayLike<T>,
|
||||
expected: ArrayLike<T>,
|
||||
msg?: string,
|
||||
): void;
|
||||
export function assertArrayIncludes(
|
||||
actual: ArrayLike<unknown>,
|
||||
expected: ArrayLike<unknown>,
|
||||
msg?: string,
|
||||
): void {
|
||||
const missing: unknown[] = [];
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
let found = false;
|
||||
for (let j = 0; j < actual.length; j++) {
|
||||
if (equal(expected[i], actual[j])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
missing.push(expected[i]);
|
||||
}
|
||||
}
|
||||
if (missing.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (!msg) {
|
||||
msg = `actual: "${_format(actual)}" expected to include: "${
|
||||
_format(expected)
|
||||
}"\nmissing: ${_format(missing)}`;
|
||||
}
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` match RegExp `expected`. If not
|
||||
* then throw.
|
||||
*/
|
||||
export function assertMatch(
|
||||
actual: string,
|
||||
expected: RegExp,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (!expected.test(actual)) {
|
||||
if (!msg) {
|
||||
msg = `actual: "${actual}" expected to match: "${expected}"`;
|
||||
}
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` not match RegExp `expected`. If match
|
||||
* then throw.
|
||||
*/
|
||||
export function assertNotMatch(
|
||||
actual: string,
|
||||
expected: RegExp,
|
||||
msg?: string,
|
||||
): void {
|
||||
if (expected.test(actual)) {
|
||||
if (!msg) {
|
||||
msg = `actual: "${actual}" expected to not match: "${expected}"`;
|
||||
}
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an assertion that `actual` object is a subset of `expected` object, deeply.
|
||||
* If not, then throw.
|
||||
*/
|
||||
export function assertObjectMatch(
|
||||
// deno-lint-ignore no-explicit-any
|
||||
actual: Record<PropertyKey, any>,
|
||||
expected: Record<PropertyKey, unknown>,
|
||||
): void {
|
||||
type loose = Record<PropertyKey, unknown>;
|
||||
const seen = new WeakMap();
|
||||
return assertEquals(
|
||||
(function filter(a: loose, b: loose): loose {
|
||||
// Prevent infinite loop with circular references with same filter
|
||||
if ((seen.has(a)) && (seen.get(a) === b)) {
|
||||
return a;
|
||||
}
|
||||
seen.set(a, b);
|
||||
// Filter keys and symbols which are present in both actual and expected
|
||||
const filtered = {} as loose;
|
||||
const entries = [
|
||||
...Object.getOwnPropertyNames(a),
|
||||
...Object.getOwnPropertySymbols(a),
|
||||
]
|
||||
.filter((key) => key in b)
|
||||
.map((key) => [key, a[key as string]]) as Array<[string, unknown]>;
|
||||
for (const [key, value] of entries) {
|
||||
// On array references, build a filtered array and filter nested objects inside
|
||||
if (Array.isArray(value)) {
|
||||
const subset = (b as loose)[key];
|
||||
if (Array.isArray(subset)) {
|
||||
filtered[key] = value
|
||||
.slice(0, subset.length)
|
||||
.map((element, index) => {
|
||||
const subsetElement = subset[index];
|
||||
if ((typeof subsetElement === "object") && (subsetElement)) {
|
||||
return filter(element, subsetElement);
|
||||
}
|
||||
return element;
|
||||
});
|
||||
continue;
|
||||
}
|
||||
} // On nested objects references, build a filtered object recursively
|
||||
else if (typeof value === "object") {
|
||||
const subset = (b as loose)[key];
|
||||
if ((typeof subset === "object") && (subset)) {
|
||||
filtered[key] = filter(value as loose, subset as loose);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filtered[key] = value;
|
||||
}
|
||||
return filtered;
|
||||
})(actual, expected),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcefully throws a failed assertion
|
||||
*/
|
||||
export function fail(msg?: string): never {
|
||||
assert(false, `Failed assertion${msg ? `: ${msg}` : "."}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function, expecting it to throw. If it does not, then it
|
||||
* throws. An error class and a string that should be included in the
|
||||
* error message can also be asserted.
|
||||
*/
|
||||
export function assertThrows<T = void>(
|
||||
fn: () => T,
|
||||
ErrorClass?: Constructor,
|
||||
msgIncludes = "",
|
||||
msg?: string,
|
||||
): void {
|
||||
let doesThrow = false;
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
if (e instanceof Error === false) {
|
||||
throw new AssertionError("A non-Error object was thrown.");
|
||||
}
|
||||
if (ErrorClass && !(e instanceof ErrorClass)) {
|
||||
msg =
|
||||
`Expected error to be instance of "${ErrorClass.name}", but was "${e.constructor.name}"${
|
||||
msg ? `: ${msg}` : "."
|
||||
}`;
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
if (
|
||||
msgIncludes &&
|
||||
!stripColor(e.message).includes(stripColor(msgIncludes))
|
||||
) {
|
||||
msg =
|
||||
`Expected error message to include "${msgIncludes}", but got "${e.message}"${
|
||||
msg ? `: ${msg}` : "."
|
||||
}`;
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
doesThrow = true;
|
||||
}
|
||||
if (!doesThrow) {
|
||||
msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function which returns a promise, expecting it to throw or reject.
|
||||
* If it does not, then it throws. An error class and a string that should be
|
||||
* included in the error message can also be asserted.
|
||||
*/
|
||||
export async function assertRejects<T = void>(
|
||||
fn: () => Promise<T>,
|
||||
ErrorClass?: Constructor,
|
||||
msgIncludes = "",
|
||||
msg?: string,
|
||||
): Promise<void> {
|
||||
let doesThrow = false;
|
||||
try {
|
||||
await fn();
|
||||
} catch (e) {
|
||||
if (e instanceof Error === false) {
|
||||
throw new AssertionError("A non-Error object was thrown or rejected.");
|
||||
}
|
||||
if (ErrorClass && !(e instanceof ErrorClass)) {
|
||||
msg =
|
||||
`Expected error to be instance of "${ErrorClass.name}", but was "${e.constructor.name}"${
|
||||
msg ? `: ${msg}` : "."
|
||||
}`;
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
if (
|
||||
msgIncludes &&
|
||||
!stripColor(e.message).includes(stripColor(msgIncludes))
|
||||
) {
|
||||
msg =
|
||||
`Expected error message to include "${msgIncludes}", but got "${e.message}"${
|
||||
msg ? `: ${msg}` : "."
|
||||
}`;
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
doesThrow = true;
|
||||
}
|
||||
if (!doesThrow) {
|
||||
msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function which returns a promise, expecting it to throw or reject.
|
||||
* If it does not, then it throws. An error class and a string that should be
|
||||
* included in the error message can also be asserted.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export { assertRejects as assertThrowsAsync };
|
||||
|
||||
/** Use this to stub out methods that will throw when invoked. */
|
||||
export function unimplemented(msg?: string): never {
|
||||
throw new AssertionError(msg || "unimplemented");
|
||||
}
|
||||
|
||||
/** Use this to assert unreachable code. */
|
||||
export function unreachable(): never {
|
||||
throw new AssertionError("unreachable");
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"headers": {
|
||||
"cache-control": "public, max-age=31536000, immutable",
|
||||
"x-amz-request-id": "5WK20JDP4JGMDA0M",
|
||||
"x-amz-id-2": "0l7bjBDsKtleLd1Grvjt2SeQI/+2ua8jGuVBG2Z/h45Meq0BMgl9J82vZTLPTrORGqpznqVFs3E=",
|
||||
"x-amz-version-id": "qW9eDWU3kybHVD1gOHGerosW7UEZaMeQ",
|
||||
"content-length": "18928",
|
||||
"content-type": "application/typescript; charset=utf-8",
|
||||
"last-modified": "Mon, 23 Aug 2021 22:19:15 GMT",
|
||||
"access-control-allow-origin": "*",
|
||||
"server": "AmazonS3",
|
||||
"date": "Tue, 24 Aug 2021 13:53:42 GMT",
|
||||
"etag": "\"3c51595bb9af62c421e371635ad7b9e9\""
|
||||
},
|
||||
"url": "https://deno.land/std@0.106.0/testing/asserts.ts"
|
||||
}
|
||||
|
|
@ -1,526 +0,0 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
// A module to print ANSI terminal colors. Inspired by chalk, kleur, and colors
|
||||
// on npm.
|
||||
//
|
||||
// ```
|
||||
// import { bgBlue, red, bold } from "https://deno.land/std/fmt/colors.ts";
|
||||
// console.log(bgBlue(red(bold("Hello world!"))));
|
||||
// ```
|
||||
//
|
||||
// This module supports `NO_COLOR` environmental variable disabling any coloring
|
||||
// if `NO_COLOR` is set.
|
||||
//
|
||||
// This module is browser compatible.
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const { Deno } = globalThis as any;
|
||||
const noColor = typeof Deno?.noColor === "boolean"
|
||||
? Deno.noColor as boolean
|
||||
: true;
|
||||
|
||||
interface Code {
|
||||
open: string;
|
||||
close: string;
|
||||
regexp: RegExp;
|
||||
}
|
||||
|
||||
/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */
|
||||
interface Rgb {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
let enabled = !noColor;
|
||||
|
||||
/**
|
||||
* Set changing text color to enabled or disabled
|
||||
* @param value
|
||||
*/
|
||||
export function setColorEnabled(value: boolean): void {
|
||||
if (noColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabled = value;
|
||||
}
|
||||
|
||||
/** Get whether text color change is enabled or disabled. */
|
||||
export function getColorEnabled(): boolean {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds color code
|
||||
* @param open
|
||||
* @param close
|
||||
*/
|
||||
function code(open: number[], close: number): Code {
|
||||
return {
|
||||
open: `\x1b[${open.join(";")}m`,
|
||||
close: `\x1b[${close}m`,
|
||||
regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies color and background based on color code and its associated text
|
||||
* @param str text to apply color settings to
|
||||
* @param code color code to apply
|
||||
*/
|
||||
function run(str: string, code: Code): string {
|
||||
return enabled
|
||||
? `${code.open}${str.replace(code.regexp, code.open)}${code.close}`
|
||||
: str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the text modified
|
||||
* @param str text to reset
|
||||
*/
|
||||
export function reset(str: string): string {
|
||||
return run(str, code([0], 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text bold.
|
||||
* @param str text to make bold
|
||||
*/
|
||||
export function bold(str: string): string {
|
||||
return run(str, code([1], 22));
|
||||
}
|
||||
|
||||
/**
|
||||
* The text emits only a small amount of light.
|
||||
* @param str text to dim
|
||||
*/
|
||||
export function dim(str: string): string {
|
||||
return run(str, code([2], 22));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text italic.
|
||||
* @param str text to make italic
|
||||
*/
|
||||
export function italic(str: string): string {
|
||||
return run(str, code([3], 23));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text underline.
|
||||
* @param str text to underline
|
||||
*/
|
||||
export function underline(str: string): string {
|
||||
return run(str, code([4], 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert background color and text color.
|
||||
* @param str text to invert its color
|
||||
*/
|
||||
export function inverse(str: string): string {
|
||||
return run(str, code([7], 27));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the text hidden.
|
||||
* @param str text to hide
|
||||
*/
|
||||
export function hidden(str: string): string {
|
||||
return run(str, code([8], 28));
|
||||
}
|
||||
|
||||
/**
|
||||
* Put horizontal line through the center of the text.
|
||||
* @param str text to strike through
|
||||
*/
|
||||
export function strikethrough(str: string): string {
|
||||
return run(str, code([9], 29));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to black.
|
||||
* @param str text to make black
|
||||
*/
|
||||
export function black(str: string): string {
|
||||
return run(str, code([30], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to red.
|
||||
* @param str text to make red
|
||||
*/
|
||||
export function red(str: string): string {
|
||||
return run(str, code([31], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to green.
|
||||
* @param str text to make green
|
||||
*/
|
||||
export function green(str: string): string {
|
||||
return run(str, code([32], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to yellow.
|
||||
* @param str text to make yellow
|
||||
*/
|
||||
export function yellow(str: string): string {
|
||||
return run(str, code([33], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to blue.
|
||||
* @param str text to make blue
|
||||
*/
|
||||
export function blue(str: string): string {
|
||||
return run(str, code([34], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to magenta.
|
||||
* @param str text to make magenta
|
||||
*/
|
||||
export function magenta(str: string): string {
|
||||
return run(str, code([35], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to cyan.
|
||||
* @param str text to make cyan
|
||||
*/
|
||||
export function cyan(str: string): string {
|
||||
return run(str, code([36], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to white.
|
||||
* @param str text to make white
|
||||
*/
|
||||
export function white(str: string): string {
|
||||
return run(str, code([37], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to gray.
|
||||
* @param str text to make gray
|
||||
*/
|
||||
export function gray(str: string): string {
|
||||
return brightBlack(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright black.
|
||||
* @param str text to make bright-black
|
||||
*/
|
||||
export function brightBlack(str: string): string {
|
||||
return run(str, code([90], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright red.
|
||||
* @param str text to make bright-red
|
||||
*/
|
||||
export function brightRed(str: string): string {
|
||||
return run(str, code([91], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright green.
|
||||
* @param str text to make bright-green
|
||||
*/
|
||||
export function brightGreen(str: string): string {
|
||||
return run(str, code([92], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright yellow.
|
||||
* @param str text to make bright-yellow
|
||||
*/
|
||||
export function brightYellow(str: string): string {
|
||||
return run(str, code([93], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright blue.
|
||||
* @param str text to make bright-blue
|
||||
*/
|
||||
export function brightBlue(str: string): string {
|
||||
return run(str, code([94], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright magenta.
|
||||
* @param str text to make bright-magenta
|
||||
*/
|
||||
export function brightMagenta(str: string): string {
|
||||
return run(str, code([95], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright cyan.
|
||||
* @param str text to make bright-cyan
|
||||
*/
|
||||
export function brightCyan(str: string): string {
|
||||
return run(str, code([96], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color to bright white.
|
||||
* @param str text to make bright-white
|
||||
*/
|
||||
export function brightWhite(str: string): string {
|
||||
return run(str, code([97], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to black.
|
||||
* @param str text to make its background black
|
||||
*/
|
||||
export function bgBlack(str: string): string {
|
||||
return run(str, code([40], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to red.
|
||||
* @param str text to make its background red
|
||||
*/
|
||||
export function bgRed(str: string): string {
|
||||
return run(str, code([41], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to green.
|
||||
* @param str text to make its background green
|
||||
*/
|
||||
export function bgGreen(str: string): string {
|
||||
return run(str, code([42], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to yellow.
|
||||
* @param str text to make its background yellow
|
||||
*/
|
||||
export function bgYellow(str: string): string {
|
||||
return run(str, code([43], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to blue.
|
||||
* @param str text to make its background blue
|
||||
*/
|
||||
export function bgBlue(str: string): string {
|
||||
return run(str, code([44], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to magenta.
|
||||
* @param str text to make its background magenta
|
||||
*/
|
||||
export function bgMagenta(str: string): string {
|
||||
return run(str, code([45], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to cyan.
|
||||
* @param str text to make its background cyan
|
||||
*/
|
||||
export function bgCyan(str: string): string {
|
||||
return run(str, code([46], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to white.
|
||||
* @param str text to make its background white
|
||||
*/
|
||||
export function bgWhite(str: string): string {
|
||||
return run(str, code([47], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright black.
|
||||
* @param str text to make its background bright-black
|
||||
*/
|
||||
export function bgBrightBlack(str: string): string {
|
||||
return run(str, code([100], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright red.
|
||||
* @param str text to make its background bright-red
|
||||
*/
|
||||
export function bgBrightRed(str: string): string {
|
||||
return run(str, code([101], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright green.
|
||||
* @param str text to make its background bright-green
|
||||
*/
|
||||
export function bgBrightGreen(str: string): string {
|
||||
return run(str, code([102], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright yellow.
|
||||
* @param str text to make its background bright-yellow
|
||||
*/
|
||||
export function bgBrightYellow(str: string): string {
|
||||
return run(str, code([103], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright blue.
|
||||
* @param str text to make its background bright-blue
|
||||
*/
|
||||
export function bgBrightBlue(str: string): string {
|
||||
return run(str, code([104], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright magenta.
|
||||
* @param str text to make its background bright-magenta
|
||||
*/
|
||||
export function bgBrightMagenta(str: string): string {
|
||||
return run(str, code([105], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright cyan.
|
||||
* @param str text to make its background bright-cyan
|
||||
*/
|
||||
export function bgBrightCyan(str: string): string {
|
||||
return run(str, code([106], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color to bright white.
|
||||
* @param str text to make its background bright-white
|
||||
*/
|
||||
export function bgBrightWhite(str: string): string {
|
||||
return run(str, code([107], 49));
|
||||
}
|
||||
|
||||
/* Special Color Sequences */
|
||||
|
||||
/**
|
||||
* Clam and truncate color codes
|
||||
* @param n
|
||||
* @param max number to truncate to
|
||||
* @param min number to truncate from
|
||||
*/
|
||||
function clampAndTruncate(n: number, max = 255, min = 0): number {
|
||||
return Math.trunc(Math.max(Math.min(n, max), min));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color using paletted 8bit colors.
|
||||
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
|
||||
* @param str text color to apply paletted 8bit colors to
|
||||
* @param color code
|
||||
*/
|
||||
export function rgb8(str: string, color: number): string {
|
||||
return run(str, code([38, 5, clampAndTruncate(color)], 39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color using paletted 8bit colors.
|
||||
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
|
||||
* @param str text color to apply paletted 8bit background colors to
|
||||
* @param color code
|
||||
*/
|
||||
export function bgRgb8(str: string, color: number): string {
|
||||
return run(str, code([48, 5, clampAndTruncate(color)], 49));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color using 24bit rgb.
|
||||
* `color` can be a number in range `0x000000` to `0xffffff` or
|
||||
* an `Rgb`.
|
||||
*
|
||||
* To produce the color magenta:
|
||||
*
|
||||
* rgb24("foo", 0xff00ff);
|
||||
* rgb24("foo", {r: 255, g: 0, b: 255});
|
||||
* @param str text color to apply 24bit rgb to
|
||||
* @param color code
|
||||
*/
|
||||
export function rgb24(str: string, color: number | Rgb): string {
|
||||
if (typeof color === "number") {
|
||||
return run(
|
||||
str,
|
||||
code(
|
||||
[38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff],
|
||||
39,
|
||||
),
|
||||
);
|
||||
}
|
||||
return run(
|
||||
str,
|
||||
code(
|
||||
[
|
||||
38,
|
||||
2,
|
||||
clampAndTruncate(color.r),
|
||||
clampAndTruncate(color.g),
|
||||
clampAndTruncate(color.b),
|
||||
],
|
||||
39,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color using 24bit rgb.
|
||||
* `color` can be a number in range `0x000000` to `0xffffff` or
|
||||
* an `Rgb`.
|
||||
*
|
||||
* To produce the color magenta:
|
||||
*
|
||||
* bgRgb24("foo", 0xff00ff);
|
||||
* bgRgb24("foo", {r: 255, g: 0, b: 255});
|
||||
* @param str text color to apply 24bit rgb to
|
||||
* @param color code
|
||||
*/
|
||||
export function bgRgb24(str: string, color: number | Rgb): string {
|
||||
if (typeof color === "number") {
|
||||
return run(
|
||||
str,
|
||||
code(
|
||||
[48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff],
|
||||
49,
|
||||
),
|
||||
);
|
||||
}
|
||||
return run(
|
||||
str,
|
||||
code(
|
||||
[
|
||||
48,
|
||||
2,
|
||||
clampAndTruncate(color.r),
|
||||
clampAndTruncate(color.g),
|
||||
clampAndTruncate(color.b),
|
||||
],
|
||||
49,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/chalk/ansi-regex/blob/2b56fb0c7a07108e5b54241e8faec160d393aedb/index.js
|
||||
const ANSI_PATTERN = new RegExp(
|
||||
[
|
||||
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
||||
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
|
||||
].join("|"),
|
||||
"g",
|
||||
);
|
||||
|
||||
/**
|
||||
* Remove ANSI escape codes from the string.
|
||||
* @param string to remove ANSI escape codes from
|
||||
*/
|
||||
export function stripColor(string: string): string {
|
||||
return string.replace(ANSI_PATTERN, "");
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"headers": {
|
||||
"content-length": "11645",
|
||||
"server": "AmazonS3",
|
||||
"last-modified": "Mon, 23 Aug 2021 22:19:13 GMT",
|
||||
"cache-control": "public, max-age=31536000, immutable",
|
||||
"x-amz-id-2": "FbRGxGOy9wqib+2NQ1OVOixb8qUPq0K2NDOPKeiteZLBpxlPowodpk7eXK5IMA4skwcEmKpYG4A=",
|
||||
"access-control-allow-origin": "*",
|
||||
"content-type": "application/typescript; charset=utf-8",
|
||||
"date": "Tue, 24 Aug 2021 13:53:42 GMT",
|
||||
"x-amz-request-id": "5WK83W9VC1FTCG2T",
|
||||
"x-amz-version-id": "kBd3kTxoXIUIFIrZfyw68XCGEhqwG0Jq",
|
||||
"etag": "\"5450ef49e6252532f88e69f2c194528a\""
|
||||
},
|
||||
"url": "https://deno.land/std@0.106.0/fmt/colors.ts"
|
||||
}
|
||||
|
|
@ -1,339 +0,0 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
// This module is browser compatible.
|
||||
|
||||
interface FarthestPoint {
|
||||
y: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export enum DiffType {
|
||||
removed = "removed",
|
||||
common = "common",
|
||||
added = "added",
|
||||
}
|
||||
|
||||
export interface DiffResult<T> {
|
||||
type: DiffType;
|
||||
value: T;
|
||||
details?: Array<DiffResult<T>>;
|
||||
}
|
||||
|
||||
const REMOVED = 1;
|
||||
const COMMON = 2;
|
||||
const ADDED = 3;
|
||||
|
||||
function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
|
||||
const common = [];
|
||||
if (A.length === 0 || B.length === 0) return [];
|
||||
for (let i = 0; i < Math.min(A.length, B.length); i += 1) {
|
||||
if (
|
||||
A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]
|
||||
) {
|
||||
common.push(A[reverse ? A.length - i - 1 : i]);
|
||||
} else {
|
||||
return common;
|
||||
}
|
||||
}
|
||||
return common;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the differences between the actual and expected values
|
||||
* @param A Actual value
|
||||
* @param B Expected value
|
||||
*/
|
||||
export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
|
||||
const prefixCommon = createCommon(A, B);
|
||||
const suffixCommon = createCommon(
|
||||
A.slice(prefixCommon.length),
|
||||
B.slice(prefixCommon.length),
|
||||
true,
|
||||
).reverse();
|
||||
A = suffixCommon.length
|
||||
? A.slice(prefixCommon.length, -suffixCommon.length)
|
||||
: A.slice(prefixCommon.length);
|
||||
B = suffixCommon.length
|
||||
? B.slice(prefixCommon.length, -suffixCommon.length)
|
||||
: B.slice(prefixCommon.length);
|
||||
const swapped = B.length > A.length;
|
||||
[A, B] = swapped ? [B, A] : [A, B];
|
||||
const M = A.length;
|
||||
const N = B.length;
|
||||
if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
|
||||
if (!N) {
|
||||
return [
|
||||
...prefixCommon.map(
|
||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||
),
|
||||
...A.map(
|
||||
(a): DiffResult<typeof a> => ({
|
||||
type: swapped ? DiffType.added : DiffType.removed,
|
||||
value: a,
|
||||
}),
|
||||
),
|
||||
...suffixCommon.map(
|
||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||
),
|
||||
];
|
||||
}
|
||||
const offset = N;
|
||||
const delta = M - N;
|
||||
const size = M + N + 1;
|
||||
const fp = new Array(size).fill({ y: -1 });
|
||||
/**
|
||||
* INFO:
|
||||
* This buffer is used to save memory and improve performance.
|
||||
* The first half is used to save route and last half is used to save diff
|
||||
* type.
|
||||
* This is because, when I kept new uint8array area to save type,performance
|
||||
* worsened.
|
||||
*/
|
||||
const routes = new Uint32Array((M * N + size + 1) * 2);
|
||||
const diffTypesPtrOffset = routes.length / 2;
|
||||
let ptr = 0;
|
||||
let p = -1;
|
||||
|
||||
function backTrace<T>(
|
||||
A: T[],
|
||||
B: T[],
|
||||
current: FarthestPoint,
|
||||
swapped: boolean,
|
||||
): Array<{
|
||||
type: DiffType;
|
||||
value: T;
|
||||
}> {
|
||||
const M = A.length;
|
||||
const N = B.length;
|
||||
const result = [];
|
||||
let a = M - 1;
|
||||
let b = N - 1;
|
||||
let j = routes[current.id];
|
||||
let type = routes[current.id + diffTypesPtrOffset];
|
||||
while (true) {
|
||||
if (!j && !type) break;
|
||||
const prev = j;
|
||||
if (type === REMOVED) {
|
||||
result.unshift({
|
||||
type: swapped ? DiffType.removed : DiffType.added,
|
||||
value: B[b],
|
||||
});
|
||||
b -= 1;
|
||||
} else if (type === ADDED) {
|
||||
result.unshift({
|
||||
type: swapped ? DiffType.added : DiffType.removed,
|
||||
value: A[a],
|
||||
});
|
||||
a -= 1;
|
||||
} else {
|
||||
result.unshift({ type: DiffType.common, value: A[a] });
|
||||
a -= 1;
|
||||
b -= 1;
|
||||
}
|
||||
j = routes[prev];
|
||||
type = routes[prev + diffTypesPtrOffset];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createFP(
|
||||
slide: FarthestPoint,
|
||||
down: FarthestPoint,
|
||||
k: number,
|
||||
M: number,
|
||||
): FarthestPoint {
|
||||
if (slide && slide.y === -1 && down && down.y === -1) {
|
||||
return { y: 0, id: 0 };
|
||||
}
|
||||
if (
|
||||
(down && down.y === -1) ||
|
||||
k === M ||
|
||||
(slide && slide.y) > (down && down.y) + 1
|
||||
) {
|
||||
const prev = slide.id;
|
||||
ptr++;
|
||||
routes[ptr] = prev;
|
||||
routes[ptr + diffTypesPtrOffset] = ADDED;
|
||||
return { y: slide.y, id: ptr };
|
||||
} else {
|
||||
const prev = down.id;
|
||||
ptr++;
|
||||
routes[ptr] = prev;
|
||||
routes[ptr + diffTypesPtrOffset] = REMOVED;
|
||||
return { y: down.y + 1, id: ptr };
|
||||
}
|
||||
}
|
||||
|
||||
function snake<T>(
|
||||
k: number,
|
||||
slide: FarthestPoint,
|
||||
down: FarthestPoint,
|
||||
_offset: number,
|
||||
A: T[],
|
||||
B: T[],
|
||||
): FarthestPoint {
|
||||
const M = A.length;
|
||||
const N = B.length;
|
||||
if (k < -N || M < k) return { y: -1, id: -1 };
|
||||
const fp = createFP(slide, down, k, M);
|
||||
while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) {
|
||||
const prev = fp.id;
|
||||
ptr++;
|
||||
fp.id = ptr;
|
||||
fp.y += 1;
|
||||
routes[ptr] = prev;
|
||||
routes[ptr + diffTypesPtrOffset] = COMMON;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
while (fp[delta + offset].y < N) {
|
||||
p = p + 1;
|
||||
for (let k = -p; k < delta; ++k) {
|
||||
fp[k + offset] = snake(
|
||||
k,
|
||||
fp[k - 1 + offset],
|
||||
fp[k + 1 + offset],
|
||||
offset,
|
||||
A,
|
||||
B,
|
||||
);
|
||||
}
|
||||
for (let k = delta + p; k > delta; --k) {
|
||||
fp[k + offset] = snake(
|
||||
k,
|
||||
fp[k - 1 + offset],
|
||||
fp[k + 1 + offset],
|
||||
offset,
|
||||
A,
|
||||
B,
|
||||
);
|
||||
}
|
||||
fp[delta + offset] = snake(
|
||||
delta,
|
||||
fp[delta - 1 + offset],
|
||||
fp[delta + 1 + offset],
|
||||
offset,
|
||||
A,
|
||||
B,
|
||||
);
|
||||
}
|
||||
return [
|
||||
...prefixCommon.map(
|
||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||
),
|
||||
...backTrace(A, B, fp[delta + offset], swapped),
|
||||
...suffixCommon.map(
|
||||
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the differences between the actual and expected strings
|
||||
* Partially inspired from https://github.com/kpdecker/jsdiff
|
||||
* @param A Actual string
|
||||
* @param B Expected string
|
||||
*/
|
||||
export function diffstr(A: string, B: string) {
|
||||
function tokenize(string: string, { wordDiff = false } = {}): string[] {
|
||||
if (wordDiff) {
|
||||
// Split string on whitespace symbols
|
||||
const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
|
||||
// Extended Latin character set
|
||||
const words =
|
||||
/^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u;
|
||||
|
||||
// Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars
|
||||
for (let i = 0; i < tokens.length - 1; i++) {
|
||||
if (
|
||||
!tokens[i + 1] && tokens[i + 2] && words.test(tokens[i]) &&
|
||||
words.test(tokens[i + 2])
|
||||
) {
|
||||
tokens[i] += tokens[i + 2];
|
||||
tokens.splice(i + 1, 2);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return tokens.filter((token) => token);
|
||||
} else {
|
||||
// Split string on new lines symbols
|
||||
const tokens = [], lines = string.split(/(\n|\r\n)/);
|
||||
|
||||
// Ignore final empty token when text ends with a newline
|
||||
if (!lines[lines.length - 1]) {
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
// Merge the content and line separators into single tokens
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (i % 2) {
|
||||
tokens[tokens.length - 1] += lines[i];
|
||||
} else {
|
||||
tokens.push(lines[i]);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
// Create details by filtering revelant word-diff for current line
|
||||
// and merge "space-diff" if surrounded by word-diff for cleaner displays
|
||||
function createDetails(
|
||||
line: DiffResult<string>,
|
||||
tokens: Array<DiffResult<string>>,
|
||||
) {
|
||||
return tokens.filter(({ type }) =>
|
||||
type === line.type || type === DiffType.common
|
||||
).map((result, i, t) => {
|
||||
if (
|
||||
(result.type === DiffType.common) && (t[i - 1]) &&
|
||||
(t[i - 1]?.type === t[i + 1]?.type) && /\s+/.test(result.value)
|
||||
) {
|
||||
result.type = t[i - 1].type;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// Compute multi-line diff
|
||||
const diffResult = diff(tokenize(`${A}\n`), tokenize(`${B}\n`));
|
||||
const added = [], removed = [];
|
||||
for (const result of diffResult) {
|
||||
if (result.type === DiffType.added) {
|
||||
added.push(result);
|
||||
}
|
||||
if (result.type === DiffType.removed) {
|
||||
removed.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute word-diff
|
||||
const aLines = added.length < removed.length ? added : removed;
|
||||
const bLines = aLines === removed ? added : removed;
|
||||
for (const a of aLines) {
|
||||
let tokens = [] as Array<DiffResult<string>>,
|
||||
b: undefined | DiffResult<string>;
|
||||
// Search another diff line with at least one common token
|
||||
while (bLines.length) {
|
||||
b = bLines.shift();
|
||||
tokens = diff(
|
||||
tokenize(a.value, { wordDiff: true }),
|
||||
tokenize(b?.value ?? "", { wordDiff: true }),
|
||||
);
|
||||
if (
|
||||
tokens.some(({ type, value }) =>
|
||||
type === DiffType.common && value.trim().length
|
||||
)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Register word-diff details
|
||||
a.details = createDetails(a, tokens);
|
||||
if (b) {
|
||||
b.details = createDetails(b, tokens);
|
||||
}
|
||||
}
|
||||
|
||||
return diffResult;
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"headers": {
|
||||
"cache-control": "public, max-age=31536000, immutable",
|
||||
"x-amz-request-id": "5WK6KHYNZZ5KA6AY",
|
||||
"content-length": "8987",
|
||||
"content-type": "application/typescript; charset=utf-8",
|
||||
"etag": "\"cf6aa0f0d1f64e912327dd59540f2c89\"",
|
||||
"server": "AmazonS3",
|
||||
"x-amz-id-2": "RQLaFsC0gkZeP3osZE1P0ryyqAJvZzkiEUdemkaCkZe9kbozl8/YSR0E9GpLoNcZD9GrYuowmaA=",
|
||||
"date": "Tue, 24 Aug 2021 13:53:42 GMT",
|
||||
"x-amz-version-id": "s.ztI5zs1eOnsBLULnHd3GCV8eow4WNa",
|
||||
"last-modified": "Mon, 23 Aug 2021 22:19:15 GMT",
|
||||
"access-control-allow-origin": "*"
|
||||
},
|
||||
"url": "https://deno.land/std@0.106.0/testing/_diff.ts"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
{"version_hash":"b9fc019860293cf104762ad4848e398e54835bf27d222c327c59b9350664a519"}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
{"version_hash":"9afb5f692fc2cd52a9b45d2dadc1c6878dadc24036e58736e00585a97211b0c4"}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
{"version_hash":"48aa556daec2980127ca50aa078501e880d5118fe3045ea105f4c64fd72f1deb"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue