UNPKG

observable-fs

Version:

Provides as Observables some of the APIs of node fs

193 lines (179 loc) 7.94 kB
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Observable, bindNodeCallback, from, Subscriber, concatMap } from 'rxjs'; import { Observer } from 'rxjs'; import { TeardownLogic } from 'rxjs'; import * as fs from 'fs'; import * as readline from 'readline'; import * as mkdirp from 'mkdirp'; import * as rimraf from 'rimraf'; import { mkdtemp, readdir, readdirSync } from 'fs'; import { join } from 'path'; import { normalizeTilde } from './path'; import path = require('path'); import { checkForMinimalNodeVersion } from './check-node-version'; // ============================= Read a file line by line and emits when completed ========================================= // returns and Observable which emits an array containing the lines of the file as strings export const readLinesObs = bindNodeCallback(_readLines); function _readLines( filePath: string, callback: (err: NodeJS.ErrnoException | null, lines: Array<string> | null) => void, ) { checkForMinimalNodeVersion('v16.0.0'); filePath = normalizeTilde(filePath); const lines = new Array<string>(); const rl = readline.createInterface({ input: fs.createReadStream(filePath), crlfDelay: Infinity, }); rl.on('line', (line: string) => { lines.push(line); }); rl.on('close', () => { callback(null, lines); }); rl.on('error', (err) => { callback(err, null); }); } // ============================= Read a file line by line and emits for each line ========================================= // returns and Observable which emits each line of the file read export const readLineObs = (filePath: string): Observable<string> => { checkForMinimalNodeVersion('v16.0.0'); filePath = normalizeTilde(filePath); return new Observable((observer: Observer<string>): TeardownLogic => { const rl = readline.createInterface({ input: fs.createReadStream(filePath), crlfDelay: Infinity, }); rl.on('line', (line: string) => { observer.next(line); }); rl.on('error', (err) => { observer.error(err); }); rl.on('close', () => { observer.complete(); }); }); }; // ====================== Writes an array of strings as lines in a file and emits when completed ========================= // Writes a file whose content is represented by an array of strings. // Each string is a line of the file. // If the directory is not present, is created // Returns an Observable which emits the name of the file written when the write operation is completed // export function writeFileObs(filePath: string, lines: Array<string>) { // return _writeFileObs(filePath, lines); // } export function writeFileObs(filePath: string, lines: Array<string>) { filePath = normalizeTilde(filePath); return new Observable((subscriber: Subscriber<string>): TeardownLogic => { const fileDir = path.dirname(filePath); mkdirp(fileDir).then( () => { const fileContent = lines.join('\n'); fs.writeFile(filePath, fileContent, (err) => { if (err) { subscriber.error(err); } subscriber.next(filePath); subscriber.complete(); }); }, (err) => { subscriber.error(err); }, ); }); } // ============ Emits the list of names of the files present in a directory and subdirectories ========= // returns an Observable that noticies the list of files found in the directory and all its subdirectories export function fileListObs(fromDirPath: string): Observable<readonly string[]> { fromDirPath = normalizeTilde(fromDirPath); return new Observable((observer: Observer<string[]>): TeardownLogic => { const files = _readdirRecursiveSync(fromDirPath); observer.next(files); observer.complete(); }); } function _readdirRecursiveSync(dir: string) { const dirs = readdirSync(dir, { withFileTypes: true }); const files = dirs.reduce((acc: string[], file) => { const filePath = join(dir, file.name); return acc.concat(file.isDirectory() ? _readdirRecursiveSync(filePath) : filePath); }, []) as string[]; return files; } // ============ Emits each name of the files present in a directory and subdirectories ========= // returns and Observable notifies each file found in the directory and all its subdirectories export function filesObs(fromDirPath: string) { fromDirPath = normalizeTilde(fromDirPath); return fileListObs(fromDirPath).pipe(concatMap((files) => from(files))); } // ============ Emits the list of names of directories present in a directory ========= // returns and Observable which emits the list of names of directories found in the directory passed in as input export function dirNamesListObs(fromDirPath: string) { fromDirPath = normalizeTilde(fromDirPath); return new Observable((observer: Observer<string[]>): TeardownLogic => { readdir(fromDirPath, { withFileTypes: true }, (err, files) => { if (err) { observer.error(err); return; } const dirs = files.filter((f) => f.isDirectory()).map((d) => d.name); observer.next(dirs); observer.complete(); }); }); } // ============ Deletes a directory and subdirectories and emits when completed ========= // returns and Observable which emits null when the directory and all its subdirectories have been deleted or an error otherwise // export function deleteDirObs(dirPath: string) { // return _rimraf(dirPath); // } // const _rimraf = Observable.bindCallback(rimraf); export function deleteDirObs(dirPath: string): Observable<string> { dirPath = normalizeTilde(dirPath); return new Observable((observer: Observer<string>): TeardownLogic => { rimraf(dirPath, (err) => { if (err) observer.error(err); observer.next(dirPath); observer.complete(); }); }); } // ============ Creates a directory and emits when completed ========= // returns and Observable which emits the name of the directory when the directory has been created or an error otherwise export function makeDirObs(dirPath: string) { dirPath = normalizeTilde(dirPath); return from(mkdirp(dirPath)); } // ============ Creates a temporary directory and emits when completed ========= // returns and Observable which emits the name of the directory when the directory has been created or an error otherwise // the direectory needs to be deleted by the caller export function makeTempDirObs(prefix: string) { return bindNodeCallback(mkdtemp)(prefix); } // ============ Appends a line to a file and emits when completed ========= // returns and Observable which emits the line appended when the line has been appended or an error otherwise export function appendFileObs(filePath: string, line: string) { filePath = normalizeTilde(filePath); return _appendFile(filePath, line); } function appendFileNode(filePath: string, line: string, cb: (err, data: string) => void) { return fs.appendFile(filePath, line, (err) => { cb(err, line); }); } const _appendFile = bindNodeCallback(appendFileNode); // ============ Deletes a file and emits when completed ========= // returns and Observable which emits the name of the file when the line has been deleted or an error otherwise export function deleteFileObs(filePath: string) { filePath = normalizeTilde(filePath); return _deleteFile(filePath); } function deleteFileNode(filePath: string, cb: (err, data: string) => void) { return fs.unlink(filePath, (err) => { cb(err, filePath); }); } const _deleteFile = bindNodeCallback(deleteFileNode);