UNPKG

@freeword/meta

Version:

Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.

197 lines 7.04 kB
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