@freeword/meta
Version:
Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.
197 lines • 7.04 kB
JavaScript
import _ /**/ from 'lodash';
import NodeFSP from 'node:fs/promises';
import PathUtils from 'node:path';
function badOutcome(err, gist, preambleMsg, tmi, pathinfo) {
const origmsg = err.message;
const extError = new Error(`${preambleMsg}: ${origmsg}`);
const errTMI = {
...(pathinfo || {}),
...tmi,
ok: false,
gist,
origmsg,
};
extError.extensions = errTMI;
return { ...errTMI, err: extError };
}
/**
* Creates directory recursively
* @param anypath - The pathname or pathinfo of the directory to create -- the `abspath` is used and NOT the `dirpath`
* @returns a GoodFilerMkdirResult or a BadFilerMkdirResult
*/
export async function mkdirp(anypath) {
const pathinfo = pathinfoFor(anypath);
if (!pathinfo.ok) {
return pathinfo;
}
try {
await NodeFSP.mkdir(pathinfo.dirpath, { recursive: true });
return { ...pathinfo, ok: true, gist: 'ok', val: pathinfo };
}
catch (err) {
return badOutcome(err, 'fsErr', 'Failed to create directory', { args: anypath }, pathinfo);
}
}
/**
* Given a pathinfo object, assemble the absolute path
*/
export function _abspathForPathparts(pathinfo) {
const dirpathStr = (typeof pathinfo.dirpath === 'string') ? pathinfo.dirpath.trim() : pathinfo.dirpath;
const barenameStr = (typeof pathinfo.barename === 'string') ? pathinfo.barename.trim() : pathinfo.barename;
if (!dirpathStr || !barenameStr) {
const outcome = badOutcome(new Error('Blank path provided'), 'blankPath', 'Blank path is not a reasonable input', { args: { pathinfo } });
throw outcome.err;
}
const filename = pathinfo.fext ? `${barenameStr}${pathinfo.fext}` : barenameStr;
try {
return PathUtils.resolve(dirpathStr, filename);
}
catch (err) {
const outcome = badOutcome(err, 'badPath', 'Failed to resolve path', { args: { pathinfo } });
throw outcome.err;
}
}
/**
* Converts a plain pathname to an absolute path
*/
export function _abspathForPathname(pathname) {
const pathnameStr = (typeof pathname === 'string') ? pathname.trim() : pathname;
if (!pathnameStr) {
const outcome = badOutcome(new Error('Blank path provided'), 'blankPath', 'Blank path is not a reasonable input', { args: { pathname } });
throw outcome.err;
}
try {
return PathUtils.resolve(pathnameStr);
}
catch (err) {
const outcome = badOutcome(err, 'badPath', 'Failed to resolve path', { args: { pathname } });
throw outcome.err;
}
}
/** Assemble pathinfo using a (possibly relative) dirpath, a barename and a file extension
* @param pathinfo - The pathinfo dna to assemble
* dirpath - The directory path (relative or absolute)
* barename - The base name of the file (without extension)
* fext - The file extension (including the dot)
* @returns A complete pathinfo object, with abspath and dirpath resolved
*/
export function pathinfoFor(anypath) {
const pathnameStr = (typeof anypath === 'string') ? anypath.trim() : anypath;
if (!pathnameStr) {
return badOutcome(new Error('Blank path provided'), 'blankPath', 'Blank path is not a reasonable input', { args: { anypath } });
}
try {
const abspath = (typeof anypath === 'string') ? _abspathForPathname(anypath) : _abspathForPathparts(anypath);
const dirpath = PathUtils.dirname(abspath);
const basename = PathUtils.basename(abspath);
const fext = PathUtils.extname(basename);
const barename = fext ? basename.slice(0, (-fext.length)) : basename;
return {
ok: true,
barename,
fext,
dirpath,
abspath,
};
}
catch (err) {
return badOutcome(err, 'badPath', 'Failed to parse path', { anypath });
}
}
/**
* Async generator that reads a file and yields each line
* Returns AsyncGenerator<string, FilerReadResult, unknown>
*/
export async function* starlines(anypath) {
const pathinfo = pathinfoFor(anypath);
if (!pathinfo.ok) {
return pathinfo;
}
let fileHandle;
try {
fileHandle = await NodeFSP.open(pathinfo.abspath, 'r');
}
catch (err) {
return badOutcome(err, 'readErr', 'Issue opening file', { args: anypath }, pathinfo);
}
try {
const buffer = Buffer.alloc(4096);
let leftover = '';
while (true) {
const { bytesRead } = await fileHandle.read(buffer, 0, buffer.length, null);
if (bytesRead === 0) {
break;
}
const chunk = leftover + buffer.toString('utf8', 0, bytesRead);
const lines = chunk.split('\n');
leftover = lines.pop();
// Yield all complete lines except the last one (which might be incomplete)
for (const line of lines) {
try {
yield line;
}
catch (err) {
const outcome = badOutcome(err, 'callerErr', 'Error processing line', { filepath: pathinfo.abspath, args: anypath });
throw outcome.err;
}
}
}
// Yield the final line if there's anything left
if (leftover) {
try {
yield leftover;
}
catch (err) {
const outcome = badOutcome(err, 'callerErr', 'Error processing final line', { filepath: pathinfo.abspath, args: anypath });
throw outcome.err;
}
}
}
finally {
await fileHandle.close();
}
return { ...pathinfo, ok: true, gist: 'ok', val: pathinfo };
}
/**
* Creates directory and writes each line from an iterable/async iterable to a file
* Returns FilerResult<PathinfoT>
*/
export async function dumptext(anypath, lines) {
const pathinfo = pathinfoFor(anypath);
if (!pathinfo.ok) {
return pathinfo;
}
const mkdirResult = await mkdirp({ ...pathinfo, abspath: pathinfo.dirpath });
if (!mkdirResult.ok) {
return mkdirResult;
}
try {
const fileHandle = await NodeFSP.open(pathinfo.abspath, 'w');
try {
for await (const line of lines) {
await fileHandle.write(line + '\n');
}
}
finally {
await fileHandle.close();
}
return { ...pathinfo, ok: true, gist: 'ok', val: pathinfo };
}
catch (err) {
return badOutcome(err, 'writeErr', 'Failed to write file', { args: anypath }, pathinfo);
}
}
/**
* Pretty prints JSON and calls dumptext. Returns FilerResult<PathinfoT>
*/
export async function dumpjson(anypath, data) {
let jsonString;
try {
jsonString = JSON.stringify(data, null, 2);
}
catch (err) {
return badOutcome(err, 'parseErr', 'Failed to stringify data to JSON', { args: anypath });
}
return await dumptext(anypath, [jsonString]);
}
//# sourceMappingURL=Filer.js.map