Skip to content

Commit

Permalink
feat(public/fs): rw and json can take a dest param
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed May 20, 2019
1 parent 680a51a commit d6bbd0a
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 45 deletions.
47 changes: 36 additions & 11 deletions src/public/fs/json.ts
@@ -1,22 +1,47 @@
import expose from '~/utils/expose';
import expose, { TExposedOverload } from '~/utils/expose';
import rw from './rw';
import { IFsUpdateOptions, TContentFn, TSource, TJsonFn } from './types';
import { IOfType } from '~/types';
import {
IFsUpdateOptions,
TReadWriteFn,
TSource,
TJsonFn,
TDestination
} from './types';

export default expose(json);
export default expose(json) as TExposedOverload<
typeof rw,
| [TSource, TDestination, TJsonFn, IFsUpdateOptions]
| [TSource, TDestination, TJsonFn]
| [TSource, TJsonFn, IFsUpdateOptions]
| [TSource, TJsonFn]
>;

function json(
src: TSource,
dest: TDestination,
fn: TJsonFn,
options?: IFsUpdateOptions
): () => Promise<void>;
function json(
src: TSource,
fn: TJsonFn,
options?: IFsUpdateOptions
): () => Promise<void>;

/**
* Reads a JSON `file` and passes it as an argument to a callback `fn`. If the callback returns other than `undefined`, **`file` will be overwritten** with the JSON parsed response. `file` can be relative to the project's directory.
* Reads a JSON `file` and passes it as an argument to a callback `fn`. If the callback returns other than `undefined`, **`file` will be overwritten** with the JSON parsed response. `file` can be relative to the project's directory. If a `dest` destination is provided, the original file won't be overwritten or removed.
* It is an *exposed* function: call `json.fn()`, which takes the same arguments, in order to execute on call.
* @returns An asynchronous function -hence, calling `json` won't have any effect until the returned function is called.
*/
function json(
file: TSource,
fn: TJsonFn,
options?: IFsUpdateOptions
): () => Promise<void> {
function json(src: TSource, ...args: any[]): () => Promise<void> {
return async () => {
const _fn: TContentFn = async (data) => {
const hasDest = typeof args[0] !== 'function';
const dest: TDestination = hasDest ? args[0] : src;
const fn: TJsonFn = hasDest ? args[1] : args[0];
const options: IFsUpdateOptions = hasDest ? args[2] : args[1];

const _fn: TReadWriteFn = async (data) => {
Object.defineProperty(data, 'json', {
enumerable: true,
get: (): IOfType<any> => (data.raw ? JSON.parse(data.raw) : undefined)
Expand All @@ -25,6 +50,6 @@ function json(
return json ? JSON.stringify(json, null, 2) : undefined;
};

return rw.fn(file, _fn, options);
return rw.fn(src, dest, _fn, options);
};
}
45 changes: 33 additions & 12 deletions src/public/fs/rw/index.ts
@@ -1,24 +1,45 @@
import expose from '~/utils/expose';
import { IFsUpdateOptions, TSource, TContentFn } from '../types';
import expose, { TExposedOverload } from '~/utils/expose';
import trunk from './rw';
import {
IFsUpdateOptions,
TSource,
TReadWriteFn,
TDestination
} from '../types';

export default expose(rw);
export default expose(rw) as TExposedOverload<
typeof rw,
| [TSource, TDestination, TReadWriteFn, IFsUpdateOptions]
| [TSource, TDestination, TReadWriteFn]
| [TSource, TReadWriteFn, IFsUpdateOptions]
| [TSource, TReadWriteFn]
>;

function rw(
src: TSource,
dest: TDestination,
fn: TReadWriteFn,
options?: IFsUpdateOptions
): () => Promise<void>;
function rw(
src: TSource,
fn: TReadWriteFn,
options?: IFsUpdateOptions
): () => Promise<void>;
/**
* Reads a `file` and passes it as an argument to a callback `fn`. If the callback returns other than `undefined`, **`file` will be overwritten** with its contents. `file` can be relative to the project's directory.
* Reads a `file` and passes it as an argument to a callback `fn`. If the callback returns other than `undefined`, **`file` will be overwritten** with its contents. `file` can be relative to the project's directory. If a `dest` destination is provided, the original file won't be overwritten or removed.
* It is an *exposed* function: call `rw.fn()`, which takes the same arguments, in order to execute on call.
* @returns An asynchronous function -hence, calling `rw` won't have any effect until the returned function is called.
*/
function rw(
file: TSource,
fn: TContentFn,
options?: IFsUpdateOptions
): () => Promise<void> {
function rw(src: TSource, ...args: any[]): () => Promise<void> {
return async () => {
const hasDest = typeof args[0] !== 'function';
src = typeof src === 'function' ? await src() : await src;
return trunk(
typeof file === 'function' ? await file() : await file,
fn,
options
src,
hasDest ? args[0] : src,
hasDest ? args[1] : args[0],
hasDest ? args[2] : args[1]
);
};
}
34 changes: 16 additions & 18 deletions src/public/fs/rw/rw.ts
@@ -1,37 +1,35 @@
import path from 'path';
import fs from 'fs-extra';
import { exists, absolute } from '~/utils/file';
import { exists } from '~/utils/file';
import confirm from '~/utils/confirm';
import { IFsUpdateOptions, TContentFn } from '../types';
import { IFsUpdateOptions, TReadWriteFn, TDestination } from '../types';
import { open } from '~/utils/errors';
import { log } from '../utils';
import { log, resolver } from '../utils';

export default async function rw(
file: string | string[],
fn: TContentFn,
src: string | string[],
dest: TDestination,
fn: TReadWriteFn,
options: IFsUpdateOptions = {}
): Promise<void> {
options = Object.assign({ overwrite: true }, options);

Array.isArray(file)
? await Promise.all(file.map((item) => each(item, fn, options)))
: await each(file, fn, options);
await resolver(src, dest, (src, dest) => each(src, dest, fn, options));
}

export async function each(
file: string,
fn: TContentFn,
src: string,
dest: string,
fn: TReadWriteFn,
options: IFsUpdateOptions
): Promise<void> {
const cwd = process.cwd();
file = absolute({ path: file, cwd });
const relative = './' + path.relative(cwd, file);
const doesExist = await exists(file, { fail: options.fail });
const raw = doesExist ? await fs.readFile(file).then(String) : undefined;
const relative = './' + path.relative(cwd, dest);
const doesExist = await exists(src, { fail: options.fail });
const raw = doesExist ? await fs.readFile(src).then(String) : undefined;

let response: string | void;
try {
response = await fn({ file, raw });
response = await fn({ src, dest, raw });
} catch (e) {
throw open(e);
}
Expand All @@ -43,7 +41,7 @@ export async function each(

if (!(await confirm(`Write "${relative}"?`, options))) return;

await fs.ensureDir(path.parse(file).dir);
await fs.writeFile(file, String(response));
await fs.ensureDir(path.dirname(dest));
await fs.writeFile(dest, String(response));
log(options, 'info')(`Written: ${relative}`);
}
10 changes: 6 additions & 4 deletions src/public/fs/types.ts
@@ -1,4 +1,4 @@
import { IOfType } from '~/types';
import { IOfType, TScript } from '~/types';

export type TSource =
| string
Expand All @@ -12,13 +12,15 @@ export type TCopyFilterFn =
| ((src: string, dest: string) => boolean)
| ((src: string, dest: string) => Promise<boolean>);

export type TContentFn = (data: {
file: string;
export type TReadWriteFn = (data: {
src: string;
dest: string;
raw?: string;
}) => string | void | Promise<string | void>;

export type TJsonFn = (data: {
file: string;
src: string;
dest: string;
raw?: string;
json?: IOfType<any>;
}) => IOfType<any> | void | Promise<IOfType<any> | void>;
Expand Down

0 comments on commit d6bbd0a

Please sign in to comment.