prague-blockchain-week/utils/engine.js

516 řádky
15 KiB
JavaScript
Surový Normální zobrazení Historie

2023-01-23 19:26:28 +01:00
import {
copy,
emptyDir,
2023-01-23 20:40:25 +01:00
ensureDir,
2023-01-23 19:26:28 +01:00
exists,
} from "https://deno.land/std@0.173.0/fs/mod.ts";
2023-01-21 22:07:27 +01:00
import { parse as tomlParse } from "https://deno.land/std@0.173.0/encoding/toml.ts";
2023-01-23 02:27:35 +01:00
import { load as yamlLoad } from "https://deno.land/x/js_yaml_port@3.14.0/js-yaml.js";
2023-01-23 17:45:00 +01:00
import { posix } from "https://deno.land/std@0.173.0/path/mod.ts";
2023-02-10 11:30:27 +01:00
import * as syncTools from "./sync.lib.js";
2023-01-29 06:22:20 +01:00
import format from "https://deno.land/x/date_fns@v2.22.1/format/index.js";
import addDays from "https://deno.land/x/date_fns@v2.22.1/addDays/index.ts";
2023-01-21 22:07:27 +01:00
2023-01-23 02:27:35 +01:00
let _silentMode = false;
2023-04-27 04:38:44 +02:00
const thumbSizes = [150, 300, 500];
2023-01-23 00:42:21 +01:00
2023-01-21 22:07:27 +01:00
export class DeConfEngine {
constructor(options = {}) {
this.options = options;
2023-02-05 16:21:19 +01:00
this.tag = this.options.tag || "dev";
2023-01-23 19:26:28 +01:00
this.srcDir = this.options.srcDir || "./data";
this.outputDir = this.options.outputDir || "./dist";
2023-01-23 00:59:31 +01:00
this.publicUrl = this.options.publicUrl || "https://data.prgblockweek.com";
2023-01-30 15:54:03 +01:00
this.exploreUrl = this.options.exploreUrl ||
"https://explore.prgblockweek.com";
2023-01-23 01:25:49 +01:00
this.githubUrl = this.options.githubUrl ||
"https://github.com/utxo-foundation/prague-blockchain-week/tree/main/data";
2023-01-23 00:42:21 +01:00
2023-01-23 03:37:00 +01:00
if (options.silent) {
2023-01-23 02:27:35 +01:00
_silentMode = true;
}
}
async init() {
2023-01-23 00:59:31 +01:00
this.entries = [];
2023-01-21 22:07:27 +01:00
for await (const f of Deno.readDir(this.srcDir)) {
2023-01-23 00:42:21 +01:00
if (!f.name.match(/^\d+$/)) continue;
2023-01-23 01:25:49 +01:00
const pkg = new DeConf_Package(f.name, this);
2023-01-23 00:42:21 +01:00
await pkg.load([this.srcDir, f.name]);
2023-01-23 02:27:35 +01:00
this.entries.push(pkg);
}
}
async build() {
await emptyDir(this.outputDir);
2023-02-05 16:33:03 +01:00
console.log(`Tag: ${this.tag}`);
2023-02-05 16:21:19 +01:00
await _textWrite([this.outputDir, "TAG"], this.tag);
2023-01-23 02:27:35 +01:00
for (const pkg of this.entries) {
2023-01-23 00:42:21 +01:00
console.table(pkg.data.events.map((e) => e.data.index), ["name"]);
await pkg.write(this.outputDir);
2023-01-21 22:07:27 +01:00
}
2023-01-23 01:03:52 +01:00
await _jsonWrite(
[this.outputDir, "index.json"],
this.entries.map((p) => ({
id: p.id,
name: p.data.index.name,
2023-01-23 01:25:49 +01:00
dataUrl: p.data.index.dataUrl,
2023-01-30 15:19:16 +01:00
exploreUrl: p.data.index.exploreUrl,
2023-02-05 16:33:03 +01:00
__time: new Date(),
__tag: this.tag,
2023-01-23 01:03:52 +01:00
})),
);
2023-01-24 00:16:57 +01:00
// write schemas
const schemaVersion = 1;
const schemas = await this.schemas(schemaVersion);
const outputSchemaDir = [this.outputDir, "schema", schemaVersion].join("/");
await emptyDir(outputSchemaDir);
console.log(`writing schema (v${schemaVersion}) ..`);
const schemaBundle = {};
for (const schema of schemas) {
await _jsonWrite(
[outputSchemaDir, schema.name + ".json"],
schema.schema,
);
schemaBundle[schema.name] = schema.schema;
}
await _jsonWrite([outputSchemaDir, "bundle.json"], {
definitions: schemaBundle,
});
2023-01-21 22:07:27 +01:00
}
2023-01-23 02:27:35 +01:00
async schemas(version = "1") {
const schemaDir = `./utils/schema/${version}`;
const arr = [];
for await (const f of Deno.readDir(schemaDir)) {
const m = f.name.match(/^(.+)\.yaml$/);
if (!m) {
continue;
}
arr.push({
name: m[1],
schema: Object.assign(
{ $id: this.schemaUrl(version, m[1]) },
await _yamlLoad([schemaDir, f.name].join("/")),
),
});
}
return arr.sort((x, y) => x.name > y.name ? 1 : -1);
}
schemaUrl(version = "1", type = "index") {
return `${this.publicUrl}/schema/${version}/${type}.json`;
}
entriesList() {
return this.entries.map((e) => e.id);
}
2023-01-23 00:42:21 +01:00
}
class DeConf_Package {
2023-01-23 01:25:49 +01:00
constructor(id, engine) {
2023-01-23 00:42:21 +01:00
this.id = id;
this.data = null;
2023-01-23 01:25:49 +01:00
this.engine = engine;
2023-02-05 16:21:19 +01:00
this.tag = engine.tag;
2023-01-24 04:02:46 +01:00
this.colMapper = {
2023-01-28 02:30:34 +01:00
places: "place",
2023-01-24 04:02:46 +01:00
events: "event",
"media-partners": "media-partner",
2023-03-24 23:46:06 +01:00
contributors: "contributor",
2023-01-24 23:12:39 +01:00
benefits: "benefit",
2023-01-28 02:30:34 +01:00
unions: "union",
2023-01-29 11:22:39 +01:00
chains: "chain",
2023-02-03 03:23:01 +01:00
"other-events": "event",
2023-01-24 04:02:46 +01:00
};
this.collections = Object.keys(this.colMapper);
2023-01-23 00:42:21 +01:00
}
async load(specDir) {
const pkg = {};
// load year index
pkg.index = await _tomlLoad([...specDir, "index.toml"].join("/"));
2023-01-23 01:25:49 +01:00
pkg.index.dataUrl = [this.engine.publicUrl, this.id].join("/");
2023-01-30 15:19:16 +01:00
pkg.index.exploreUrl = [this.engine.exploreUrl, this.id].join("/");
2023-01-23 01:25:49 +01:00
pkg.index.dataGithubUrl = [this.engine.githubUrl, this.id].join("/");
2023-01-23 02:27:35 +01:00
//console.log(`\n##\n## [${pkg.index.name}] \n##`);
2023-01-23 00:42:21 +01:00
// load sub-events
2023-01-24 04:02:46 +01:00
for (const colPlural of this.collections) {
pkg[colPlural] = await this.loadCollection(specDir, colPlural);
}
2023-01-23 15:02:55 +01:00
2023-01-23 00:42:21 +01:00
this.data = pkg;
}
async write(dir) {
const outputDir = [dir, this.id].join("/");
await emptyDir(outputDir);
2023-01-23 15:54:53 +01:00
await this.assetsWrite(outputDir);
2023-01-23 00:59:31 +01:00
await _jsonWrite([outputDir, "index.json"], this.toJSON());
2023-01-23 00:42:21 +01:00
}
2023-01-23 15:02:55 +01:00
async loadCollection(specDir, type) {
const arr = [];
for await (const ef of Deno.readDir([...specDir, type].join("/"))) {
2023-01-25 13:15:21 +01:00
if (ef.name.match(/^_/)) continue;
2023-01-23 15:02:55 +01:00
const m = ef.name.match(/^([\w\d\-]+)(\.toml|)$/);
if (!m) continue;
2023-05-15 15:06:52 +02:00
const ev = new DeConf_Collection(type, m[1], this.engine);
2023-04-18 01:51:02 +02:00
try {
await ev.load([...specDir, type, ef.name]);
} catch (e) {
2023-04-18 14:10:32 +02:00
throw new Error(`[item=${m[1]}]: ${e}`);
2023-04-18 01:51:02 +02:00
}
2023-01-23 15:02:55 +01:00
arr.push(ev);
}
return arr;
}
2023-01-23 15:54:53 +01:00
async assetsWrite(outputDir) {
for (const colName of this.collections) {
2023-01-23 19:26:28 +01:00
const dir = [outputDir, "assets", colName].join("/");
await emptyDir(dir);
2023-01-23 15:54:53 +01:00
for (const item of this.data[colName]) {
await item.assetsWrite(
dir,
2023-01-23 19:26:28 +01:00
[this.engine.publicUrl, this.id, "assets", colName].join(
2023-01-23 15:54:53 +01:00
"/",
),
);
}
}
}
2023-01-23 00:42:21 +01:00
toJSON() {
return Object.assign({ id: this.id }, this.data.index, {
2023-01-28 02:30:34 +01:00
...Object.fromEntries(
Object.keys(this.colMapper).map((col) => {
return [col, this.data[col]];
}),
),
2023-02-05 16:21:19 +01:00
__time: new Date(),
__tag: this.tag,
2023-01-23 00:42:21 +01:00
});
}
}
2023-01-23 15:02:55 +01:00
class DeConf_Collection {
2023-05-15 15:06:52 +02:00
constructor(type, id, engine) {
2023-01-23 15:54:53 +01:00
this.type = type;
2023-01-23 00:42:21 +01:00
this.id = id;
this.data = null;
2023-01-23 03:37:00 +01:00
this.dir = null;
2023-01-28 02:30:34 +01:00
this.assets = ["logo", "photo"];
2023-03-28 05:03:25 +02:00
this.haveSync = false;
2023-04-19 00:18:10 +02:00
this.dataFile = null;
2023-05-15 15:06:52 +02:00
this.engine = engine;
2023-01-23 00:42:21 +01:00
}
2023-01-23 14:25:24 +01:00
async load(path) {
let fn;
2023-01-23 14:36:10 +01:00
if (path[path.length - 1].match(/^(.+)\.toml$/)) {
fn = path;
2023-01-23 14:25:24 +01:00
} else {
this.dir = path.join("/");
2023-01-23 14:36:10 +01:00
fn = [...path, "index.toml"];
2023-01-23 14:25:24 +01:00
}
2023-01-23 14:36:10 +01:00
2023-04-19 00:18:10 +02:00
this.dataFile = [this.dir, "data.json"].join("/");
2023-01-23 14:25:24 +01:00
const efIndex = await _tomlLoad(fn.join("/"));
2023-01-28 01:20:24 +01:00
const hash = await _makeHash([this.type, this.id].join(":"));
2023-01-23 03:37:00 +01:00
const data = {
2023-01-28 01:20:24 +01:00
index: { id: this.id, hash, ...efIndex },
2023-01-23 00:42:21 +01:00
};
2023-02-03 03:23:01 +01:00
if (["events", "other-events"].includes(this.type)) {
2023-01-29 06:22:20 +01:00
// add Event Segments
if (!data.index.segments) {
data.index.segments = [];
for (let i = 0; i < data.index.days; i++) {
data.index.segments.push({
date: format(addDays(new Date(data.index.date), i), "yyyy-MM-dd"),
times: data.index.times || "09:00-18:00",
});
}
}
2023-04-26 03:15:06 +02:00
for (const sg of data.index.segments) {
if (sg.remote) {
continue;
}
2023-01-29 06:22:20 +01:00
const [sstart, send] = sg.times.split("-");
sg.startTime = (new Date(`${sg.date}T${sstart}`)).toISOString();
const endDate = send <= sstart
? format(addDays(new Date(sg.date), 1), "yyyy-MM-dd")
: sg.date;
sg.endTime = (new Date(`${endDate}T${send}`)).toISOString();
}
}
2023-05-15 15:06:52 +02:00
if (
this.dir &&
(!data.index.hidden ||
this.engine.options.hiddenAllowed &&
this.engine.options.hiddenAllowed.includes("bitcoin-prague"))
) {
2023-01-23 14:40:09 +01:00
const syncDataFn = [this.dir, "data.json"].join("/");
2023-01-23 14:25:24 +01:00
if (await exists(syncDataFn)) {
data.sync = await _jsonLoad(syncDataFn);
}
2023-01-23 15:54:53 +01:00
for (const asset of this.assets) {
if (data.index[asset]) {
const assetFn = [this.dir, data.index[asset]].join("/");
if (!await exists(assetFn)) {
throw new Error(`Asset not exists: ${assetFn}`);
}
}
}
2023-01-23 03:37:00 +01:00
}
2023-03-28 05:03:25 +02:00
// check if sync file exists
this.syncFile = [this.dir, "_sync.js"].join("/");
if (await exists(this.syncFile)) {
this.haveSync = true;
}
2023-01-23 03:37:00 +01:00
this.data = data;
}
2023-04-19 00:18:10 +02:00
async optimizeImages() {
for (const as of this.assets) {
if (this.data.index[as]) {
2023-04-19 01:27:38 +02:00
await this.optimizeImageFile(this.data.index[as]);
2023-04-19 00:18:10 +02:00
}
}
if (this.data.index?.speakers) {
2023-04-19 01:27:38 +02:00
for (const s of this.data.index.speakers) {
await this.optimizeImageFile(s.photo);
}
2023-04-19 00:18:10 +02:00
}
2023-04-19 01:27:38 +02:00
if (this.data.sync?.speakers) {
for (const s of this.data.sync.speakers) {
await this.optimizeImageFile(s.photo);
}
2023-04-19 00:18:10 +02:00
}
}
2023-04-19 01:27:38 +02:00
async optimizeImageFile(fn) {
if (!fn) {
return null;
2023-04-19 00:47:59 +02:00
}
2023-04-19 01:27:38 +02:00
const src = [this.dir, fn].join("/");
2023-04-19 01:58:20 +02:00
const extname = posix.extname(src);
2023-04-19 01:27:38 +02:00
const dest = [this.dir, fn.replace(/\.([^\.]+)$/, ".op.webp")].join("/");
2023-04-27 03:52:46 +02:00
if (!await exists(dest)) {
if (extname === ".webp") {
await _fileCopy(src, dest);
console.log(`${dest} copied`);
return true;
}
await _imageOptimalizedWrite(src, dest);
console.log(`${dest} writed`);
}
2023-04-27 04:38:44 +02:00
const info = await _imageWebPInfo(dest);
2023-04-27 03:52:46 +02:00
if (!info) {
2023-04-27 04:38:44 +02:00
return null;
2023-04-19 00:18:10 +02:00
}
2023-04-27 05:16:24 +02:00
const longer = info.width > info.height ? "w" : "h";
const ratio = longer === "h"
? (info.width / info.height)
: (info.height / info.width);
2023-04-27 04:38:44 +02:00
for (const sz of thumbSizes) {
2023-04-27 05:16:24 +02:00
const pxs = longer === "h"
? [sz, Math.round(sz / ratio)]
: [Math.round(sz / ratio), sz];
2023-04-27 04:38:44 +02:00
//console.log(`size=${sz} px_orig=${[info.width, info.height]} px=${pxs}`)
2023-04-27 03:52:46 +02:00
//console.log(info.width, info.height, ratio, sz, cheight)
2023-04-27 04:38:44 +02:00
const szDest = [this.dir, fn.replace(/\.([^\.]+)$/, `-${sz}px.op.webp`)]
.join("/");
2023-04-27 03:52:46 +02:00
if (!await exists(szDest)) {
2023-04-27 04:38:44 +02:00
await _imageOptimalizedWrite(dest, szDest, pxs);
2023-04-27 03:52:46 +02:00
console.log(`${szDest} writed`);
}
2023-04-19 01:58:20 +02:00
}
2023-04-19 00:18:10 +02:00
}
2023-01-23 03:37:00 +01:00
async sync() {
2023-03-28 05:03:25 +02:00
if (!this.haveSync) return null;
2023-01-23 03:37:00 +01:00
if (!_silentMode) console.log(`syncing ${this.id} ..`);
2023-03-28 05:03:25 +02:00
const module = await import("../" + this.syncFile);
2023-01-23 03:37:00 +01:00
// data
if (module.data) {
const data = await module.data(syncTools);
2023-01-23 04:37:12 +01:00
if (!JSON.stringify(data)) {
return null;
}
2023-01-23 20:40:25 +01:00
if (data.speakers) {
2023-05-09 18:20:14 +02:00
data.speakers = data.speakers.sort((x, y) => x.id > y.id ? 1 : -1);
2023-01-23 20:40:25 +01:00
const photosDir = [this.dir, "photos"].join("/");
2023-01-26 02:17:29 +01:00
await ensureDir(photosDir);
2023-01-23 20:40:25 +01:00
for (const sp of data.speakers) {
if (!sp.photoUrl) continue;
const ext = await posix.extname(sp.photoUrl);
2023-05-24 14:37:05 +02:00
const nameId = sp.id || sp.name.toLowerCase().replace(/ /g, "-");
2023-01-23 20:40:25 +01:00
const dir = [photosDir, "speakers"].join("/");
2023-04-19 01:10:30 +02:00
const ffn = (sp.id ? sp.id : nameId) + ext.replace(/\?.+$/, "");
2023-01-26 02:17:29 +01:00
const fn = [dir, ffn].join("/");
2023-01-26 02:24:15 +01:00
if (await exists(fn)) {
sp.photo = ["photos", "speakers", ffn].join("/");
continue;
2023-01-28 01:20:24 +01:00
}
2023-01-23 20:40:25 +01:00
await ensureDir(dir);
2023-01-26 02:17:29 +01:00
const photoFetch = await fetch(sp.photoUrl);
2023-04-18 14:10:32 +02:00
if (!photoFetch.body) {
continue;
}
2023-04-19 00:18:10 +02:00
const tmpfile = [dir, ffn].join("/");
2023-04-18 14:10:32 +02:00
const file = await Deno.open(tmpfile, { write: true, create: true });
await photoFetch.body.pipeTo(file.writable);
2023-04-19 00:18:10 +02:00
//await _imageOptimalizedWrite(tmpfile, fn);
2023-04-18 14:10:32 +02:00
console.log(`${fn} writed`);
sp.photo = ["photos", "speakers", ffn].join("/");
2023-01-23 20:40:25 +01:00
}
}
2023-04-19 00:18:10 +02:00
await _jsonWrite(this.dataFile, data);
2023-01-23 20:40:25 +01:00
this.data.sync = data;
2023-01-23 03:37:00 +01:00
}
2023-01-23 00:42:21 +01:00
}
2023-01-23 15:54:53 +01:00
async assetsWrite(outputDir, publicUrl) {
2023-04-19 01:27:38 +02:00
const x = { ...this.data.sync, ...this.data.index };
2023-04-27 04:38:44 +02:00
const writeImage = async (fn, outDir, fnRename = null) => {
const srcFile = [this.dir, fn].join("/");
if (await exists(srcFile)) {
2023-04-27 05:16:24 +02:00
const outFile = [outDir, fnRename || posix.basename(fn)].join("/");
2023-04-27 04:38:44 +02:00
await _fileCopy(srcFile, outFile);
}
2023-04-27 05:16:24 +02:00
};
2023-04-27 04:38:44 +02:00
const writeImageBundle = async (src, outDir) => {
await ensureDir(outDir);
//await writeImage(src, outDir)
//await writeImage(src.replace(/\.(.+)$/, '.op.webp'), outDir, posix.basename(src).replace(/\.(.+)$/, `.webp`))
2023-04-27 05:16:24 +02:00
await writeImage(
src.replace(/\.(.+)$/, "-500px.op.webp"),
outDir,
posix.basename(src).replace(/\.(.+)$/, `.webp`),
);
2023-04-27 04:38:44 +02:00
for (const sz of thumbSizes) {
2023-04-27 05:16:24 +02:00
await writeImage(
src.replace(/\.(.+)$/, `-${sz}px.op.webp`),
outDir,
posix.basename(src).replace(/\.(.+)$/, `_${sz}px.webp`),
);
2023-04-27 04:38:44 +02:00
}
2023-04-27 05:16:24 +02:00
};
2023-01-23 15:54:53 +01:00
for (const asset of this.assets) {
2023-04-19 00:54:51 +02:00
if (!x[asset]) continue;
2023-04-27 05:16:24 +02:00
const outDir = [outputDir, this.id].join("/");
await emptyDir(outDir);
await writeImageBundle(x[asset], outDir);
const fnOut = [this.id, x[asset].replace(/\.[^.]+$/, ".webp")].join("/");
2023-01-23 16:23:04 +01:00
const url = [publicUrl, fnOut].join("/");
2023-01-23 15:54:53 +01:00
this.data.index[asset] = url;
}
2023-04-18 14:10:32 +02:00
const speakersCol = this.data.sync
? this.data.sync.speakers
: this.data.index.speakers;
if (speakersCol) {
2023-01-23 20:40:25 +01:00
const outDir = [outputDir, this.id, "photos", "speakers"].join("/");
2023-04-18 14:10:32 +02:00
for (const sp of speakersCol) {
2023-01-23 20:40:25 +01:00
if (!sp.photo) continue;
2023-04-27 05:16:24 +02:00
await writeImageBundle(sp.photo, outDir);
2023-04-27 04:38:44 +02:00
sp.photoUrl = [
2023-04-27 05:16:24 +02:00
publicUrl,
this.id,
"photos",
"speakers",
posix.basename(sp.photo).replace(/\.[^.]+$/, ".webp"),
].join("/");
}
2023-01-23 20:40:25 +01:00
}
2023-01-23 15:54:53 +01:00
}
2023-04-19 01:58:20 +02:00
opResolve(fn) {
return [fn.replace(/[^\.]+$/, "op.webp"), fn.replace(/[^\.]+$/, "webp")];
}
2023-01-23 00:42:21 +01:00
toJSON() {
2023-01-23 03:37:00 +01:00
return Object.assign({ id: this.id }, this.data.index, this.data.sync);
2023-01-23 00:42:21 +01:00
}
}
2023-04-27 03:52:46 +02:00
async function _imageWebPInfo(src) {
2023-04-27 04:38:44 +02:00
const p = Deno.run({
cmd: ["webpinfo", src],
stdout: "piped",
stderr: "piped",
});
2023-04-27 03:52:46 +02:00
await p.status();
2023-04-27 04:38:44 +02:00
const info = new TextDecoder().decode(await p.output());
2023-04-27 03:52:46 +02:00
if (!info.trim()) {
2023-04-27 04:38:44 +02:00
console.log(src);
return null;
2023-04-27 03:52:46 +02:00
}
2023-05-22 08:39:23 +02:00
if (info.match("Errors detected.")) {
return false
}
2023-04-27 03:52:46 +02:00
return {
size: Number(info.match(/File size:\s+(\d+)/)[1]),
width: Number(info.match(/Width: (\d+)/)[1]),
2023-04-27 04:38:44 +02:00
height: Number(info.match(/Height: (\d+)/)[1]),
};
2023-04-27 03:52:46 +02:00
}
2023-04-18 14:10:32 +02:00
async function _imageOptimalizedWrite(src, dest, resize = null) {
const cmd = [
"cwebp",
2023-04-27 03:52:46 +02:00
...(resize ? ["-resize", resize[0], resize[1]] : []),
2023-04-18 14:10:32 +02:00
"-q",
"80",
src,
"-o",
dest,
];
2023-04-19 01:58:20 +02:00
const p = Deno.run({ cmd, stdout: "piped", stderr: "piped" });
await p.status();
2023-04-27 04:38:44 +02:00
//const err = new TextDecoder().decode(await p.stderrOutput())
//console.log(err)
2023-04-18 14:10:32 +02:00
}
2023-01-23 15:54:53 +01:00
async function _fileCopy(from, to) {
2023-01-23 18:12:33 +01:00
await copy(from, to, { overwrite: true });
2023-01-23 15:58:25 +01:00
if (!_silentMode) {
console.log(`${from} copied to ${to}`);
}
return true;
2023-01-23 15:54:53 +01:00
}
2023-01-23 00:42:21 +01:00
async function _tomlLoad(fn) {
return tomlParse(await Deno.readTextFile(fn));
}
2023-01-23 02:27:35 +01:00
async function _yamlLoad(fn) {
return yamlLoad(await Deno.readTextFile(fn));
}
2023-01-23 00:42:21 +01:00
async function _jsonWrite(fn, data) {
if (Array.isArray(fn)) {
fn = fn.join("/");
}
await Deno.writeTextFile(fn, JSON.stringify(data, null, 2));
if (!_silentMode) {
console.log(`${fn} writed`);
2023-01-21 22:07:27 +01:00
}
2023-01-23 00:42:21 +01:00
return true;
2023-01-21 22:07:27 +01:00
}
2023-02-05 16:21:19 +01:00
async function _textWrite(fn, text) {
if (Array.isArray(fn)) {
fn = fn.join("/");
}
await Deno.writeTextFile(fn, text);
}
2023-01-23 03:37:00 +01:00
async function _jsonLoad(fn) {
return JSON.parse(await Deno.readTextFile(fn));
}
2023-01-28 01:20:24 +01:00
async function _makeHash(str) {
return Array.from(
new Uint8Array(
await crypto.subtle.digest("SHA-256", (new TextEncoder()).encode(str)),
),
).map((b) => b.toString(16).padStart(2, "0")).join("");
}