mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-22 00:11:02 -05:00
Added utility methods for reading zipped files
This commit is contained in:
parent
e407797735
commit
cf10a77ae0
2 changed files with 127 additions and 18 deletions
|
@ -1,7 +1,8 @@
|
|||
import { $i } from "@/utils/collections";
|
||||
import { FileNotFoundError } from "@/utils/errors";
|
||||
import glob from "fast-glob";
|
||||
import { ReadStream, createReadStream, existsSync, readFileSync as readFileNodeSync, statSync } from "node:fs";
|
||||
import StreamZip from "node-stream-zip";
|
||||
import { PathLike, ReadStream, createReadStream, existsSync, readFileSync as readFileNodeSync, statSync } from "node:fs";
|
||||
import { readFile as readFileNode } from "node:fs/promises";
|
||||
import { basename, dirname } from "node:path";
|
||||
|
||||
|
@ -195,13 +196,9 @@ export function findFilesSync(pattern: string | string[]): FileInfo[] {
|
|||
*
|
||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||
*/
|
||||
export async function readFile(pattern: string): Promise<Buffer> {
|
||||
const files = await glob(pattern);
|
||||
if (!files?.length) {
|
||||
throw new FileNotFoundError(pattern);
|
||||
}
|
||||
|
||||
return await readFileNode(files[0]);
|
||||
export async function readFile(pattern: PathLike): Promise<Buffer> {
|
||||
const file = await getFileName(pattern);
|
||||
return await readFileNode(file);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,10 +211,45 @@ export async function readFile(pattern: string): Promise<Buffer> {
|
|||
*
|
||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||
*/
|
||||
export async function readAllText(pattern: string, encoding?: BufferEncoding): Promise<string> {
|
||||
export async function readAllText(pattern: PathLike, encoding?: BufferEncoding): Promise<string> {
|
||||
return (await readFile(pattern)).toString(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a zipped file and returns its content as a Buffer.
|
||||
*
|
||||
* @param pattern - The glob pattern used to locate the zip archive.
|
||||
* @param entry - The entry name of the file within the zip archive.
|
||||
*
|
||||
* @returns A promise that resolves to a Buffer containing the file contents.
|
||||
*/
|
||||
export async function readZippedFile(pattern: PathLike, entry: string) : Promise<Buffer> {
|
||||
const file = await getFileName(pattern);
|
||||
|
||||
let zip = undefined as StreamZip.StreamZipAsync;
|
||||
try {
|
||||
// Dude, it's not my constructor, calm down.
|
||||
// eslint-disable-next-line new-cap
|
||||
zip = new StreamZip.async({ file });
|
||||
return await zip.entryData(entry);
|
||||
} finally {
|
||||
await zip?.close().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a zipped file and returns its content as a string.
|
||||
*
|
||||
* @param pattern - The glob pattern used to locate the zip archive.
|
||||
* @param entry - The entry name of the file within the zip archive.
|
||||
* @param encoding - The optional encoding to use for reading the file. Defaults to `utf8`.
|
||||
*
|
||||
* @returns A promise that resolves to a string containing the file contents.
|
||||
*/
|
||||
export async function readAllZippedText(pattern: PathLike, entry: string, encoding?: BufferEncoding) : Promise<string> {
|
||||
return (await readZippedFile(pattern, entry)).toString(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the first file matching the specified glob pattern synchronously.
|
||||
*
|
||||
|
@ -227,13 +259,9 @@ export async function readAllText(pattern: string, encoding?: BufferEncoding): P
|
|||
*
|
||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||
*/
|
||||
export function readFileSync(pattern: string): Buffer {
|
||||
const files = glob.sync(pattern);
|
||||
if (!files?.length) {
|
||||
throw new FileNotFoundError(pattern);
|
||||
}
|
||||
|
||||
return readFileNodeSync(files[0]);
|
||||
export function readFileSync(pattern: PathLike): Buffer {
|
||||
const file = getFileNameSync(pattern);
|
||||
return readFileNodeSync(file);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,6 +274,50 @@ export function readFileSync(pattern: string): Buffer {
|
|||
*
|
||||
* @throws {FileNotFoundError} - If no files matching the pattern are found.
|
||||
*/
|
||||
export function readAllTextSync(pattern: string, encoding?: BufferEncoding): string {
|
||||
export function readAllTextSync(pattern: PathLike, encoding?: BufferEncoding): string {
|
||||
return readFileSync(pattern).toString(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the first file that matches the specified glob pattern.
|
||||
*
|
||||
* @param pattern - The file path or glob pattern.
|
||||
*
|
||||
* @returns The name of the first matching file.
|
||||
*
|
||||
* @throws {FileNotFoundError} - If no matching file is found.
|
||||
*/
|
||||
async function getFileName(pattern: PathLike): Promise<string> {
|
||||
if (existsSync(pattern)) {
|
||||
return pattern.toString();
|
||||
}
|
||||
|
||||
const files = await glob(pattern.toString());
|
||||
if (files?.[0]) {
|
||||
return files[0];
|
||||
}
|
||||
|
||||
throw new FileNotFoundError(pattern.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously retrieves the name of the first file that matches the specified glob pattern.
|
||||
*
|
||||
* @param pattern - The file path or glob pattern.
|
||||
*
|
||||
* @returns The name of the first matching file.
|
||||
*
|
||||
* @throws {FileNotFoundError} - If no matching file is found.
|
||||
*/
|
||||
function getFileNameSync(pattern: PathLike): string {
|
||||
if (existsSync(pattern)) {
|
||||
return pattern.toString();
|
||||
}
|
||||
|
||||
const files = glob.sync(pattern.toString());
|
||||
if (files?.[0]) {
|
||||
return files[0];
|
||||
}
|
||||
|
||||
throw new FileNotFoundError(pattern.toString());
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { statSync } from "node:fs";
|
||||
import mockFs from "mock-fs";
|
||||
import { zipContent } from "../../../utils/zip-utils";
|
||||
import {
|
||||
FileInfo,
|
||||
fileEquals,
|
||||
|
@ -7,15 +8,18 @@ import {
|
|||
findFilesSync,
|
||||
readAllText,
|
||||
readAllTextSync,
|
||||
readAllZippedText,
|
||||
readFile,
|
||||
readFileSync,
|
||||
readZippedFile,
|
||||
} from "@/utils/io/file-info";
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
mockFs({
|
||||
"path/to": {
|
||||
"test.txt": "test",
|
||||
"test.json": JSON.stringify({ foo: 42 }),
|
||||
"test.zip": await zipContent("test", "test.txt"),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -281,3 +285,36 @@ describe("readAllTextSync", () => {
|
|||
expect(() => readAllTextSync("path/from/*.txt")).toThrow(/path\/from\/\*\.txt/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readZippedFile", () => {
|
||||
test("reads the contents of the first matching file", async () => {
|
||||
const content = await readZippedFile("path/to/*.zip", "test.txt");
|
||||
|
||||
expect(Buffer.isBuffer(content)).toBe(true);
|
||||
expect(content.toString()).toEqual("test");
|
||||
});
|
||||
|
||||
test("throws if no files were found", async () => {
|
||||
await expect(readZippedFile("path/from/*.zip", "")).rejects.toThrow(/path\/from\/\*\.zip/);
|
||||
});
|
||||
|
||||
test("throws if the entry does not exist within the zip", async () => {
|
||||
await expect(readZippedFile("path/to/test.zip", "not-test.txt")).rejects.toThrow(/Entry not found/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readAllZippedText", () => {
|
||||
test("reads the contents of the first matching file", async () => {
|
||||
const content = await readAllZippedText("path/to/*.zip", "test.txt");
|
||||
|
||||
expect(content).toEqual("test");
|
||||
});
|
||||
|
||||
test("throws if no files were found", async () => {
|
||||
await expect(readAllZippedText("path/from/*.zip", "")).rejects.toThrow(/path\/from\/\*\.zip/);
|
||||
});
|
||||
|
||||
test("throws if the entry does not exist within the zip", async () => {
|
||||
await expect(readAllZippedText("path/to/test.zip", "not-test.txt")).rejects.toThrow(/Entry not found/);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue