Add Base32 encoding support and enhance caching logic

This commit is contained in:
2026-04-06 10:20:50 +08:00
parent 9e18169744
commit 2df31a718e
2 changed files with 71 additions and 7 deletions

View File

@@ -2,6 +2,7 @@
// - https://docs.deno.com/runtime/fundamentals/testing/ // - https://docs.deno.com/runtime/fundamentals/testing/
import {decodeBase64, encodeBase64} from "jsr:@std/encoding/base64"; import {decodeBase64, encodeBase64} from "jsr:@std/encoding/base64";
import {encodeBase32} from "jsr:@std/encoding/base32";
import {dirname, fromFileUrl} from "jsr:@std/path"; import {dirname, fromFileUrl} from "jsr:@std/path";
import {toArrayBuffer} from "jsr:@std/streams"; import {toArrayBuffer} from "jsr:@std/streams";
import {spawn, SpawnOptionsWithoutStdio} from "node:child_process"; import {spawn, SpawnOptionsWithoutStdio} from "node:child_process";
@@ -812,12 +813,14 @@ export function joinPath(path1: string, ...paths: string[]): string {
if (paths != null && paths.length > 0) { if (paths != null && paths.length > 0) {
for (let i = 0; i < paths.length; i++) { for (let i = 0; i < paths.length; i++) {
const path2 = paths[i]; const path2 = paths[i];
if (basePath.endsWith("/") && path2.startsWith("/")) { if (path2) {
basePath += path2.substring(1); if (basePath.endsWith("/") && path2.startsWith("/")) {
} else if (basePath.endsWith("/") || path2.startsWith("/")) { basePath += path2.substring(1);
basePath += path2; } else if (basePath.endsWith("/") || path2.startsWith("/")) {
} else { basePath += path2;
basePath += "/" + path2; } else {
basePath += "/" + path2;
}
} }
} }
} }
@@ -952,6 +955,10 @@ export function uint8ArrayToHexString(uint8: Uint8Array): string {
.join(""); .join("");
} }
export function uint8ArrayToBase32String(uint8: Unit8Array): string {
return encodeBase32(uint8).replaceAll(/=/g, "").toLowerCase();
}
export function hexStringToUint8Array(hex: string): Uint8Array { export function hexStringToUint8Array(hex: string): Uint8Array {
hex = hex.trim(); hex = hex.trim();
if (hex.startsWith("0x") || hex.startsWith("0X")) { if (hex.startsWith("0x") || hex.startsWith("0X")) {
@@ -1149,6 +1156,12 @@ export async function sha256AndHexOfString(input: string): Promise<string> {
return uint8ArrayToHexString(new Uint8Array(await sha256OfString(input))); return uint8ArrayToHexString(new Uint8Array(await sha256OfString(input)));
} }
export async function sha256AndBase32OfString(input: string): Promise<string> {
return uint8ArrayToBase32String(
new Uint8Array(await sha256OfString(input)),
);
}
export async function sha256OfString(input: string): Promise<ArrayBuffer> { export async function sha256OfString(input: string): Promise<ArrayBuffer> {
const data = new TextEncoder().encode(input); const data = new TextEncoder().encode(input);
return await crypto.subtle.digest("SHA-256", data); return await crypto.subtle.digest("SHA-256", data);
@@ -1165,6 +1178,8 @@ export interface FetchFileWithCacheMeta {
export interface FetchFileWithCacheOptions { export interface FetchFileWithCacheOptions {
baseDir?: string; baseDir?: string;
hierarchicalCacheDir?: boolean;
base32Filename?: boolean;
tag?: string; tag?: string;
timeoutMillis?: number; timeoutMillis?: number;
check_cache_file?: ( check_cache_file?: (
@@ -1190,9 +1205,13 @@ export async function fetchFileWithCache(
url: string, url: string,
options?: FetchFileWithCacheOptions, options?: FetchFileWithCacheOptions,
): Promise<FetchFileWithCacheMeta> { ): Promise<FetchFileWithCacheMeta> {
const urlSha256 = await sha256AndHexOfString(url); const urlSha256 = options?.base32Filename
? await sha256AndBase32OfString(url)
: await sha256AndHexOfString(url);
const fileCacheDir = joinPath( const fileCacheDir = joinPath(
resolveFilename(options?.baseDir ?? BASE_FILE_CACHE_DIR), resolveFilename(options?.baseDir ?? BASE_FILE_CACHE_DIR),
options?.hierarchicalCacheDir ? urlSha256.substring(0, 2) : null,
options?.hierarchicalCacheDir ? urlSha256.substring(2, 4) : null,
urlSha256, urlSha256,
); );
const fileCacheMetaFile = fileCacheDir + ".meta"; const fileCacheMetaFile = fileCacheDir + ".meta";

45
single-scripts/get-rfc.ts Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env -S deno run -A
import { parseArgs } from "jsr:@std/cli/parse-args";
import {
args,
exit,
fetchFileWithCache,
readFileToString,
} from "https://global.hatter.ink/script/get/@62/deno-commons-mod.ts";
const flags = parseArgs(args(), {
boolean: ["help"],
string: ["id"],
});
function printHelp() {
console.log("get-rfc.ts --id <RFC ID>");
}
if (flags.help) {
printHelp();
exit(0);
}
if (flags.id) {
const rfcId = parseInt(flags.id, 10);
if (isNaN(rfcId)) {
console.error("Invalid RFC id");
exit(1);
}
await getAndPrintRfc(rfcId);
} else {
printHelp();
exit(1);
}
async function getAndPrintRfc(rfcId: number): Promise<void> {
const url = `https://play.hatter.me/ietf/rfc${rfcId}.txt`;
const fileMeta = await fetchFileWithCache(url, {
base32Filename: true,
hierarchicalCacheDir: true,
});
const content = await readFileToString(fileMeta.cache_full_path);
console.log(content);
}