Skip to content

Commit

Permalink
implement context
Browse files Browse the repository at this point in the history
  • Loading branch information
vedantroy committed Mar 24, 2021
1 parent 773be01 commit 88b0792
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 19 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Re-organize files with Javascript.
**Delete all backups and create new ones**\
`jsmv src "f.endsWith('.backup') ? f.del() : f.mv(f + '.backup')"`

**Number files**\
`jsmv src "f.mv(ctx.i++ + '.js')" --ctx "{i: 0}"`

Look at the [e2e tests](./tests/e2e) for more examples.

===

## How to Use
Expand All @@ -24,7 +29,10 @@ The command format is `jsmv dir_name snippet_or_file_name <FLAGS>`
You can either provide a JS snippet or a file that contains a JS snippet.

The JS snippet has access to the variable `f`. By default `f` is described by
the [defaultFileObj class](./defaultFileObj.ts). It's well commented.
the [defaultFileObj class](./defaultFileObj.ts). It's well commented. There's
also `ctx` which is a variable that is persistent between every execution of the
snippet. It is possible to add/delete properties on `ctx`, but re-assignment is
impossible.

_Entry_ = The current file/directory being operated on.

Expand All @@ -44,6 +52,25 @@ If you want `f` to have custom functionality you can implement your own
`JSMOVE_FILEOBJ_PATH` is an optional environment variable to a file path with a
custom FileObj class. `--fileObj` overrides `JSMOVE_FILEOBJ_PATH`.

## --ctx and JSMOVE_CTX

`--ctx` is a snippet of JS-object-notation (e.g `{foo: "bar"}`) that will be
executed to create the initial context. Without it, the initial context is
empty.

`JSMOVE_CTX` operates similarly to `JSMOVE_FILEOBJ_PATH` except it's not a path.

### Guidelines

All custom FileObj classes should implement the `getOp` operation. File system
operations should not be done directly inside the FileObj class. Rather, an `Op`
should be returned by `getOp`. This has several benefits:

- Atomicity (explained [here](#Atomicity))
- jsmv can do checks like
- ensuring files are only deleted if `-d` is passed
- ensuring moves don't overwrite files unless `-o` is passed

## Order of Operations

### Atomicity
Expand Down
3 changes: 2 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const HELP_MSG = `
Usage: [binary_name] directory snippet_or_file <FLAGS> [--fileobj=<file>]
Usage: [binary_name] directory snippet_or_file <FLAGS> [--fileobj=<file>] [--ctx=<js_snippet>]
FLAGS:
-h, --help: Show this message
Expand All @@ -9,4 +9,5 @@ FLAGS:
-q, --quiet Don't print unless there's an error
-o, --overwrite Allow overwriting files with the copy or move operations
--fileObj A file with a custom "FileObj" class
--ctx A snippet of JS to create the initial context
`;
1 change: 1 addition & 0 deletions src/defaultFileObj.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class FileObj extends String implements IFileObj {
isDir: boolean;
isFile: boolean;
cliArgs: unknown;
ctx: any;
}) {
const absPath = path.join(absDirPath, name);
super(absPath);
Expand Down
61 changes: 44 additions & 17 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,6 @@ const optHelp = args.help || false;
const optQuiet = args.quiet || false;
const optOverwrite = args.overwrite || false;

const configPath = Deno.env.get("JSMOVE_FILEOBJ_PATH");
if (configPath !== undefined && !args.fileObj) {
if (!fs.existsSync(configPath)) {
console.warn(`No config at "${configPath}"`);
} else {
args.fileObj = configPath;
}
}

if (optHelp) {
console.log(HELP_MSG);
Deno.exit(0);
Expand All @@ -59,15 +50,39 @@ const snippet = fs.existsSync(snippetOrFileName)
: snippetOrFileName;
// Sand-boxing is decent: `this` is undefined, global variables don't seem to be mutated
// Probably has holes though.
const createMutation: (f: FileObj) => void = new Function("f", snippet) as any;
const createMutation: (f: FileObj, ctx: any) => void = new Function(
"f",
"ctx",
snippet,
) as any;

let FileObjClass = FileObj;

if (args.fileObj) {
if (args.fileObj === true) {
fatal("--fileObj requires an argument");
function envOrOpt(
optName: string,
isPath: boolean,
cb: (optVal: string) => void,
) {
const envName = `JSMOVE_${optName.toUpperCase()}${isPath ? "_PATH" : null}`;
const val = Deno.env.get(envName);
if (isPath && val !== undefined && !args[optName]) {
if (!fs.existsSync(val)) {
console.warn(`No file at "${val}" provided by ${envName}`);
} else {
args[optName] = val;
}
}

if (args[optName]) {
if (args[optName] === true) {
fatal(`--${optName} requires an argument`);
}
cb(args[optName]);
}
const text = Deno.readTextFileSync(args.fileObj);
}

envOrOpt("fileObj", true, (p) => {
const text = Deno.readTextFileSync(p);
const f = new Function("FileObj", "fs", "path", text);
FileObjClass = f(FileObj, fs, path);
if (typeof FileObjClass !== "function") {
Expand All @@ -76,7 +91,18 @@ if (args.fileObj) {
if (typeof FileObjClass.prototype.getOp !== "function") {
throw new Error(`Custom fileObj class must have a "getOp" function`);
}
}
});

let ctx = {};
envOrOpt("ctx", false, (text) => {
const toEval = `return ${text}`;
try {
const f = new Function(toEval);
ctx = f();
} catch (e) {
fatal(`Could not create/execute function created from "${toEval}"`);
}
});

function forEach(
dir: string,
Expand All @@ -92,6 +118,7 @@ function forEach(
cliArgs: args,
isDir: isDirectory,
isFile: isFile,
ctx,
}),
);
if (optRecursive) {
Expand All @@ -108,7 +135,7 @@ function forEach(
const toDelete = new Set<string>();
if (optDelete) {
forEach(dir, (oldPath) => {
createMutation(oldPath);
createMutation(oldPath, ctx);
if (oldPath.getOp()?.type === OpType.DELETE) {
toDelete.add(oldPath.path);
return true;
Expand All @@ -122,7 +149,7 @@ const newFiles = new Set<string>();

forEach(dir, (oldPath) => {
if (toDelete.has(oldPath.path)) return;
createMutation(oldPath);
createMutation(oldPath, ctx);
const op = oldPath.getOp();
if (op === null) return;
if (op.type === OpType.DELETE) {
Expand Down
Empty file.
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions tests/e2e/number-files-with-ctx/cmd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"params": ["f.mv(ctx.i++ + '.js')", "--ctx", "{i: 0}"]
}
21 changes: 21 additions & 0 deletions tests/e2e/number-files-with-ctx/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"type": "dir",
"name": "temp",
"children": [
{
"type": "file",
"name": "1.js",
"text": ""
},
{
"type": "file",
"name": "0.js",
"text": ""
},
{
"type": "file",
"name": "2.js",
"text": ""
}
]
}

0 comments on commit 88b0792

Please sign in to comment.