351 lines
9.7 KiB
TypeScript
351 lines
9.7 KiB
TypeScript
// Reference:
|
|
// - https://docs.deno.com/runtime/fundamentals/testing/
|
|
|
|
import { assert } from "jsr:@std/assert/assert";
|
|
import { assertEquals } from "jsr:@std/assert";
|
|
import { dirname } from "https://deno.land/std@0.208.0/path/mod.ts";
|
|
|
|
export async function sleep(timeoutMillis: number): Promise<void> {
|
|
await new Promise(resolve => setTimeout(resolve, timeoutMillis))
|
|
}
|
|
|
|
export function compareVersion(ver1: string, ver2: string): 0 | 1 | -1 {
|
|
if (ver1 === ver2) return 0;
|
|
const ver1Parts = ver1.split(".");
|
|
const ver2Parts = ver2.split(".");
|
|
const ver1Main = parseInt(ver1Parts[0]);
|
|
const ver2Main = parseInt(ver2Parts[0]);
|
|
if (ver1Main > ver2Main) return 1;
|
|
if (ver1Main < ver2Main) return -1;
|
|
const ver1Second = parseInt(ver1Parts[1]);
|
|
const ver2Second = parseInt(ver2Parts[1]);
|
|
if (ver1Second > ver2Second) return 1;
|
|
if (ver1Second < ver2Second) return -1;
|
|
const ver1Third = parseInt(ver1Parts[2]);
|
|
const ver2Third = parseInt(ver2Parts[2]);
|
|
if (ver1Third > ver2Third) return 1;
|
|
if (ver1Third < ver2Third) return -1;
|
|
return 0;
|
|
}
|
|
|
|
export function isOn(val: string | undefined): boolean {
|
|
if ((val === null) || (val === undefined)) {
|
|
return false;
|
|
}
|
|
const lowerVal = val.toLowerCase();
|
|
return lowerVal === "on" || lowerVal === "yes" || lowerVal === "1" ||
|
|
lowerVal === "true";
|
|
}
|
|
|
|
export function isEnvOn(envKey: string): boolean {
|
|
return isOn(Deno.env.get(envKey));
|
|
}
|
|
|
|
export function formatHumanTime(timeMillis: number): string {
|
|
const times = [];
|
|
if (timeMillis < 1000) {
|
|
return `${timeMillis}ms`;
|
|
}
|
|
const timeSecs = Math.floor(timeMillis / 1000);
|
|
const timeSecsLow = timeSecs % 60;
|
|
if (timeSecsLow > 0) {
|
|
times.push(`${timeSecsLow}s`);
|
|
}
|
|
const timeMinutes = Math.floor(timeSecs / 60);
|
|
const timeMinutesLow = timeMinutes % 60;
|
|
if (timeMinutesLow > 0) {
|
|
times.push(`${timeMinutesLow}m`);
|
|
}
|
|
const timeHours = Math.floor(timeMinutes / 60);
|
|
const timeHoursLow = timeHours % 24;
|
|
if (timeHoursLow > 0) {
|
|
times.push(`${timeHoursLow}h`);
|
|
}
|
|
const timeDays = Math.floor(timeHours / 24);
|
|
if (timeDays > 0) {
|
|
times.push(`${timeDays}d`);
|
|
}
|
|
return times.reverse().join(" ");
|
|
}
|
|
|
|
export function formatSize(size: number): string {
|
|
if (size < 0) {
|
|
return "N/A";
|
|
}
|
|
if (size == 0) {
|
|
return "0B";
|
|
}
|
|
const sizes = [];
|
|
const bytesLow = size % 1024;
|
|
if (bytesLow > 0) {
|
|
sizes.push(`${bytesLow}B`);
|
|
}
|
|
const kb = Math.floor(size / 1024);
|
|
const kbLow = kb % 1024;
|
|
if (kbLow > 0) {
|
|
sizes.push(`${kbLow}KiB`);
|
|
}
|
|
const mb = Math.floor(kb / 1024);
|
|
const mbLow = mb % 1024;
|
|
if (mbLow > 0) {
|
|
sizes.push(`${mbLow}MiB`);
|
|
}
|
|
const gb = Math.floor(mb / 1024);
|
|
if (gb > 0) {
|
|
sizes.push(`${gb}GiB`);
|
|
}
|
|
return sizes.reverse().join(" ");
|
|
}
|
|
|
|
export function formatSize2(size: number): string {
|
|
if (size < 0) {
|
|
return "N/A";
|
|
}
|
|
if (size < 1024) {
|
|
return `${size}B`;
|
|
}
|
|
if (size < 1024 * 1024) {
|
|
return `${formatNumber(size / 1024)}KiB`;
|
|
}
|
|
return `${formatNumber(size / (1024 * 1024))}MiB`;
|
|
}
|
|
|
|
export function formatPercent(a: number, b: number): string {
|
|
if (b == null || b <= 0) {
|
|
return "N/A";
|
|
}
|
|
return formatNumber((a * 100) / b) + "%";
|
|
}
|
|
|
|
export function formatNumber(num: number): string {
|
|
const p = num.toString();
|
|
const pointIndex = p.indexOf(".");
|
|
if (pointIndex < 0) {
|
|
return p + ".00";
|
|
}
|
|
const decimal = p.substring(pointIndex + 1);
|
|
const decimalPart = decimal.length == 1
|
|
? (decimal + "0")
|
|
: decimal.substring(0, 2);
|
|
return p.substring(0, pointIndex) + "." + decimalPart;
|
|
}
|
|
|
|
export async function clearLastLine() {
|
|
await printLastLine("");
|
|
}
|
|
|
|
export async function printLastLine(line: string) {
|
|
await Deno.stdout.write(
|
|
new TextEncoder().encode(
|
|
`\x1b[1000D${line}\x1b[K`,
|
|
),
|
|
);
|
|
}
|
|
|
|
class Term {
|
|
constructor() {
|
|
}
|
|
|
|
blink(message: string): string {
|
|
return `\x1b[5m${message}\x1b[0m`;
|
|
}
|
|
|
|
bold(message: string): string {
|
|
return `\x1b[1m${message}\x1b[0m`;
|
|
}
|
|
|
|
red(message: string): string {
|
|
return `\x1b[31m${message}\x1b[0m`;
|
|
}
|
|
|
|
green(message: string): string {
|
|
return `\x1b[32m${message}\x1b[0m`;
|
|
}
|
|
|
|
yellow(message: string): string {
|
|
return `\x1b[33m${message}\x1b[0m`;
|
|
}
|
|
}
|
|
|
|
export const term = new Term();
|
|
|
|
function pad(message: string, length: number): string {
|
|
if (message.length >= length) {
|
|
return message;
|
|
}
|
|
return message + " ".repeat(length - message.length);
|
|
}
|
|
const LOGGER_PREFIX_LEN: number = 8;
|
|
class Logger {
|
|
constructor() {
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
success(...data: any[]) {
|
|
this.log(
|
|
term.bold(term.green(`[${pad("SUCCESS", LOGGER_PREFIX_LEN)}]`)),
|
|
data,
|
|
);
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
error(...data: any[]) {
|
|
this.log(
|
|
term.bold(term.red(`[${pad("ERROR", LOGGER_PREFIX_LEN)}]`)),
|
|
data,
|
|
);
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
warn(...data: any[]) {
|
|
this.log(
|
|
term.bold(term.yellow(`[${pad("WARN", LOGGER_PREFIX_LEN)}]`)),
|
|
data,
|
|
);
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
warning(...data: any[]) {
|
|
this.log(
|
|
term.blink(
|
|
term.bold(term.yellow(`[${pad("WARN", LOGGER_PREFIX_LEN)}]`)),
|
|
),
|
|
data,
|
|
);
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
info(...data: any[]) {
|
|
this.log(term.bold(`[${pad("INFO", LOGGER_PREFIX_LEN)}]`), data);
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
debug(...data: any[]) {
|
|
this.log(`[${pad("DEBUG", LOGGER_PREFIX_LEN)}]`, data);
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
log(prefix: string, data: any[]) {
|
|
const args = [prefix];
|
|
for (let i = 0; i < data.length; i++) {
|
|
args.push(data[i]);
|
|
}
|
|
console.log.apply(console, args);
|
|
}
|
|
}
|
|
|
|
export const log = new Logger();
|
|
|
|
export function getHomeDir(): string | null {
|
|
if (Deno.build.os === "windows") {
|
|
return Deno.env.get("USERPROFILE") || Deno.env.get("HOMEDRIVE") + Deno.env.get("HOMEPATH") || null;
|
|
}
|
|
return Deno.env.get("HOME") || null;
|
|
}
|
|
|
|
export function resolveFilename(filename: string): string {
|
|
if (filename.startsWith("~/")) {
|
|
return getHomeDir() + filename.substring(1);
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
export async function existsPath(path: string): Promise<boolean> {
|
|
try {
|
|
const stat = await Deno.stat(path);
|
|
return stat != null;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function readFileToString(filename: string): Promise<string | null> {
|
|
try {
|
|
return await Deno.readTextFile(resolveFilename(filename));
|
|
} catch (e) {
|
|
if (e instanceof Error && e.name == 'NotFound') {
|
|
return null;
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export async function writeStringToFile(filename: string, data: string | null): Promise<void> {
|
|
const newFilename = resolveFilename(filename);
|
|
if (data == null) {
|
|
if (await existsPath(newFilename)) {
|
|
await Deno.remove(newFilename)
|
|
}
|
|
} else {
|
|
const parentDirname = dirname(newFilename);
|
|
if (!await existsPath(parentDirname)) {
|
|
await Deno.mkdir(parentDirname, {recursive: true});
|
|
}
|
|
await Deno.writeTextFile(newFilename, data);
|
|
}
|
|
}
|
|
|
|
Deno.test("isOn", () => {
|
|
assertEquals(false, isOn(undefined));
|
|
assertEquals(false, isOn(""));
|
|
assertEquals(true, isOn("true"));
|
|
assertEquals(true, isOn("TRUE"));
|
|
assertEquals(true, isOn("yes"));
|
|
assertEquals(true, isOn("YES"));
|
|
assertEquals(true, isOn("on"));
|
|
assertEquals(true, isOn("ON"));
|
|
assertEquals(true, isOn("1"));
|
|
});
|
|
|
|
Deno.test("formatHumanTime", () => {
|
|
assertEquals("0ms", formatHumanTime(0));
|
|
assertEquals("1ms", formatHumanTime(1));
|
|
assertEquals("1s", formatHumanTime(1000));
|
|
assertEquals("1s", formatHumanTime(1001));
|
|
assertEquals("1m", formatHumanTime(60001));
|
|
assertEquals("1m 1s", formatHumanTime(61001));
|
|
assertEquals("1h", formatHumanTime(3600000));
|
|
assertEquals("1h 1s", formatHumanTime(3601000));
|
|
assertEquals("1h 1m 1s", formatHumanTime(3661000));
|
|
});
|
|
|
|
Deno.test("formatSize", () => {
|
|
assertEquals("N/A", formatSize(-1));
|
|
assertEquals("0B", formatSize(0));
|
|
assertEquals("1B", formatSize(1));
|
|
assertEquals("1KiB", formatSize(1024));
|
|
assertEquals("1KiB 1B", formatSize(1024 + 1));
|
|
assertEquals("1MiB 1KiB 1B", formatSize(1024 * 1024 + 1024 + 1));
|
|
assertEquals(
|
|
"1GiB 1MiB 1KiB 1B",
|
|
formatSize(1024 * 1024 * 1024 + 1024 * 1024 + 1024 + 1),
|
|
);
|
|
});
|
|
|
|
Deno.test("formatSize2", () => {
|
|
assertEquals("N/A", formatSize2(-1));
|
|
assertEquals("0B", formatSize2(0));
|
|
assertEquals("1B", formatSize2(1));
|
|
assertEquals("1.00KiB", formatSize2(1024));
|
|
assertEquals("10.00KiB", formatSize2(1024 * 10));
|
|
assertEquals("1.00MiB", formatSize2(1024 * 1024));
|
|
});
|
|
|
|
Deno.test("formatPercent", () => {
|
|
assertEquals("N/A", formatPercent(100, -1));
|
|
assertEquals("N/A", formatPercent(100, 0));
|
|
assertEquals("N/A", formatPercent(100, 0));
|
|
assertEquals("10.00%", formatPercent(10, 100));
|
|
assertEquals("11.00%", formatPercent(11, 100));
|
|
assertEquals("1.10%", formatPercent(11, 1000));
|
|
assertEquals("0.10%", formatPercent(1, 1000));
|
|
assertEquals("0.00%", formatPercent(1, 100000));
|
|
assertEquals("100.00%", formatPercent(100, 100));
|
|
});
|
|
|
|
Deno.test("sleep", async () => {
|
|
const t1 = new Date().getTime();
|
|
await sleep(1000)
|
|
const t2 = new Date().getTime();
|
|
assert(Math.abs(1000 - (t2 - t1)) < 20);
|
|
}); |